1 |
/* Compiler for language definition files. |
2 |
* |
3 |
* IRC Services is copyright (c) 1996-2009 Andrew Church. |
4 |
* E-mail: <achurch@achurch.org> |
5 |
* Parts written by Andrew Kempe and others. |
6 |
* This program is free but copyrighted software; see the file GPL.txt for |
7 |
* details. |
8 |
*/ |
9 |
|
10 |
/* |
11 |
* A language definition file contains all strings which Services sends to |
12 |
* users in a particular language. A language file may contain comments |
13 |
* (lines beginning with "#"--note that inline comments are not allowed!) |
14 |
* and blank lines. All other lines must adhere to the following format: |
15 |
* |
16 |
* Each string definition begins with the C name of a message (as defined |
17 |
* in the file "index"--see below). This must be alone on a line, preceded |
18 |
* and followed by no blank space. Following this line are zero or more |
19 |
* lines of text; each line of text must begin with exactly one tab |
20 |
* character, which is discarded. Newlines are retained in the strings, |
21 |
* except the last newline in the text, which is discarded. A message with |
22 |
* no text is replaced by a null pointer in the array (not an empty |
23 |
* string). |
24 |
* |
25 |
* All messages in the program are listed, one per line, in the "index" |
26 |
* file. No comments or blank lines are permitted in that file. The index |
27 |
* file can be generated from a language file with a command like: |
28 |
* grep '^[A-Z]' en_us.l >index |
29 |
* |
30 |
* This program takes one parameter, the name of the language file. It |
31 |
* generates a compiled language file whose name is created by removing any |
32 |
* extension on the source file on the input filename. |
33 |
* |
34 |
* You may also pass a "-w" option to print warnings for missing strings. |
35 |
* |
36 |
* This program isn't very flexible, because it doesn't need to be, but |
37 |
* anyone who wants to try making it more flexible is welcome to. |
38 |
*/ |
39 |
|
40 |
#include <stdio.h> |
41 |
#include <stdlib.h> |
42 |
#include <string.h> |
43 |
#include <unistd.h> |
44 |
|
45 |
/* CR/LF values--used instead of '\r' and '\n' to avoid platform-dependent |
46 |
* messiness */ |
47 |
#define CR ((char)13) |
48 |
#define LF ((char)10) |
49 |
|
50 |
int numstrings = 0; /* Number of strings we should have */ |
51 |
char **stringnames; /* Names of the strings (from index file) */ |
52 |
char **strings; /* Strings we have loaded */ |
53 |
|
54 |
int linenum = 0; /* Current line number in input file */ |
55 |
|
56 |
/*************************************************************************/ |
57 |
|
58 |
/* Read the index file and load numstrings and stringnames. Return -1 on |
59 |
* error, 0 on success. */ |
60 |
|
61 |
static int read_index_file(void) |
62 |
{ |
63 |
FILE *f; |
64 |
char buf[256]; |
65 |
int i; |
66 |
|
67 |
if (!(f = fopen("index", "r"))) { |
68 |
perror("fopen(index)"); |
69 |
return -1; |
70 |
} |
71 |
while (fgets(buf, sizeof(buf), f)) |
72 |
numstrings++; |
73 |
if (!(stringnames = calloc(sizeof(char *), numstrings))) { |
74 |
perror("calloc(stringnames)"); |
75 |
return -1; |
76 |
} |
77 |
if (!(strings = calloc(sizeof(char *), numstrings))) { |
78 |
perror("calloc(strings)"); |
79 |
return -1; |
80 |
} |
81 |
fseek(f, 0, SEEK_SET); |
82 |
i = 0; |
83 |
while (fgets(buf, sizeof(buf), f)) { |
84 |
if (buf[strlen(buf)-1] == LF) |
85 |
buf[strlen(buf)-1] = 0; |
86 |
if (buf[strlen(buf)-1] == CR) |
87 |
buf[strlen(buf)-1] = 0; |
88 |
if (!(stringnames[i++] = strdup(buf))) { |
89 |
perror("strdup()"); |
90 |
return -1; |
91 |
} |
92 |
} |
93 |
fclose(f); |
94 |
return 0; |
95 |
} |
96 |
|
97 |
/*************************************************************************/ |
98 |
|
99 |
/* Return the index of a string name in stringnames, or -1 if not found. */ |
100 |
|
101 |
static int stringnum(const char *name) |
102 |
{ |
103 |
int i; |
104 |
|
105 |
for (i = 0; i < numstrings; i++) { |
106 |
if (strcmp(stringnames[i], name) == 0) |
107 |
return i; |
108 |
} |
109 |
return -1; |
110 |
} |
111 |
|
112 |
/*************************************************************************/ |
113 |
|
114 |
/* Read a non-comment, non-blank line from the input file. Return NULL at |
115 |
* end of file. */ |
116 |
|
117 |
static char *readline(FILE *f) |
118 |
{ |
119 |
static char buf[1024]; |
120 |
char *s; |
121 |
|
122 |
do { |
123 |
if (!(fgets(buf, sizeof(buf), f))) |
124 |
return NULL; |
125 |
linenum++; |
126 |
} while (*buf == '#' || *buf == CR || *buf == LF); |
127 |
s = buf + strlen(buf)-1; |
128 |
if (*s == LF) |
129 |
*s-- = 0; |
130 |
if (*s == CR) |
131 |
*s = 0; |
132 |
return buf; |
133 |
} |
134 |
|
135 |
/*************************************************************************/ |
136 |
|
137 |
/* Write a 32-bit value to a file in big-endian order. Returns 0 on |
138 |
* success, -1 on error. |
139 |
*/ |
140 |
|
141 |
static int fput32(long val, FILE *f) |
142 |
{ |
143 |
if (fputc(val>>24, f) == EOF || |
144 |
fputc(val>>16, f) == EOF || |
145 |
fputc(val>> 8, f) == EOF || |
146 |
fputc(val , f) == EOF |
147 |
) { |
148 |
return -1; |
149 |
} else { |
150 |
return 0; |
151 |
} |
152 |
} |
153 |
|
154 |
/*************************************************************************/ |
155 |
|
156 |
int main(int ac, char **av) |
157 |
{ |
158 |
char *filename = NULL, *s; |
159 |
char langname[254], outfile[256]; |
160 |
FILE *in, *out; |
161 |
int warn = 0; |
162 |
int retval = 0; |
163 |
int curstring = -2, i; |
164 |
char *line; |
165 |
int maxerr = 50; /* Max errors before we bail out */ |
166 |
long pos, totalsize; |
167 |
|
168 |
if (ac >= 2 && strcmp(av[1], "-w") == 0) { |
169 |
warn = 1; |
170 |
av[1] = av[2]; |
171 |
ac--; |
172 |
} |
173 |
if (ac != 2) { |
174 |
fprintf(stderr, "Usage: %s [-w] <lang-file>\n", av[0]); |
175 |
return 1; |
176 |
} |
177 |
filename = av[1]; |
178 |
s = strrchr(filename, '.'); |
179 |
if (!s) |
180 |
s = filename + strlen(filename); |
181 |
if (s-filename > sizeof(langname)-3) |
182 |
s = filename + sizeof(langname)-1; |
183 |
strncpy(langname, filename, s-filename); |
184 |
langname[s-filename] = 0; |
185 |
sprintf(outfile, "%s", langname); |
186 |
|
187 |
if (read_index_file() < 0) |
188 |
return 1; |
189 |
if (!(in = fopen(filename, "r"))) { |
190 |
perror(filename); |
191 |
return 1; |
192 |
} |
193 |
if (!(out = fopen(outfile, "w"))) { |
194 |
perror(outfile); |
195 |
return 1; |
196 |
} |
197 |
|
198 |
while (maxerr > 0 && (line = readline(in)) != NULL) { |
199 |
if (*line == '\t') { |
200 |
if (curstring == -2) { |
201 |
fprintf(stderr, "%s:%d: Junk at beginning of file\n", |
202 |
filename, linenum); |
203 |
retval = 1; |
204 |
} else if (curstring >= 0) { |
205 |
line++; |
206 |
i = strings[curstring] ? strlen(strings[curstring]) : 0; |
207 |
if (!(strings[curstring] = |
208 |
realloc(strings[curstring], i+strlen(line)+2))) { |
209 |
fprintf(stderr, "%s:%d: Out of memory!\n", |
210 |
filename, linenum); |
211 |
return 2; |
212 |
} |
213 |
sprintf(strings[curstring]+i, "%s\n", line); |
214 |
} |
215 |
|
216 |
} else { |
217 |
if ((curstring = stringnum(line)) < 0) { |
218 |
fprintf(stderr, "%s:%d: Unknown string name `%s'\n", |
219 |
filename, linenum, line); |
220 |
retval = 1; |
221 |
maxerr--; |
222 |
} else if (strings[curstring]) { |
223 |
fprintf(stderr, "%s:%d: Duplicate occurrence of string `%s'\n", |
224 |
filename, linenum, line); |
225 |
retval = 1; |
226 |
maxerr--; |
227 |
} else { |
228 |
if (!(strings[curstring] = malloc(1))) { |
229 |
fprintf(stderr, "%s:%d: Out of memory!\n", |
230 |
filename, linenum); |
231 |
return 2; |
232 |
} |
233 |
*strings[curstring] = 0; |
234 |
} |
235 |
|
236 |
} |
237 |
} |
238 |
|
239 |
fclose(in); |
240 |
|
241 |
if (retval != 0) { |
242 |
if (maxerr == 0) |
243 |
fprintf(stderr, "%s:%d: Too many errors!\n", filename, linenum); |
244 |
fclose(out); |
245 |
unlink(outfile); |
246 |
return retval; |
247 |
} |
248 |
|
249 |
totalsize = 0; |
250 |
for (i = 0; i < numstrings; i++) { |
251 |
if (strings[i]) { |
252 |
if (*strings[i]) |
253 |
strings[i][strlen(strings[i])-1] = 0; /* kill last \n */ |
254 |
if (*strings[i]) |
255 |
totalsize += strlen(strings[i]) + 1; |
256 |
} else if (warn) { |
257 |
fprintf(stderr, "%s: String `%s' missing\n", filename, |
258 |
stringnames[i]); |
259 |
} |
260 |
} |
261 |
|
262 |
if (fput32(numstrings, out) < 0 || fput32(totalsize, out) < 0) { |
263 |
perror("fwrite()"); |
264 |
retval = 1; |
265 |
} |
266 |
for (i = 0; i < numstrings && retval == 0; i++) { |
267 |
if (strings[i] && *strings[i]) { |
268 |
if (fwrite(strings[i], strlen(strings[i])+1, 1, out) != 1) { |
269 |
perror("fwrite()"); |
270 |
retval = 1; |
271 |
} |
272 |
} |
273 |
} |
274 |
pos = 0; |
275 |
for (i = 0; i < numstrings && retval == 0; i++) { |
276 |
if (strings[i] && *strings[i]) { |
277 |
if (fput32(pos, out) < 0) { |
278 |
perror("fwrite()"); |
279 |
retval = 1; |
280 |
} |
281 |
pos += strlen(strings[i]) + 1; |
282 |
} else { |
283 |
if (fput32(-1, out) < 0) { |
284 |
perror("fwrite()"); |
285 |
retval = 1; |
286 |
} |
287 |
} |
288 |
} |
289 |
|
290 |
if (fclose(out) == EOF && retval == 0) { |
291 |
perror("fclose()"); |
292 |
retval = 1; |
293 |
} |
294 |
if (retval) |
295 |
unlink(outfile); |
296 |
return retval; |
297 |
} |
298 |
|
299 |
/*************************************************************************/ |
300 |
|
301 |
/* |
302 |
* Local variables: |
303 |
* c-file-style: "stroustrup" |
304 |
* c-file-offsets: ((case-label . *) (statement-case-intro . *)) |
305 |
* indent-tabs-mode: nil |
306 |
* End: |
307 |
* |
308 |
* vim: expandtab shiftwidth=4: |
309 |
*/ |