1 /*
2 *
3 * Copyright (c) 2015, Red Hat, Inc.
4 * Copyright (c) 2015, Masatake YAMATO
5 *
6 * Author: Masatake YAMATO <yamato@redhat.com>
7 *
8 * This source code is released for free distribution under the terms of the
9 * GNU General Public License version 2 or (at your option) any later version.
10 *
11 */
12
13 #include "general.h"
14 #include "debug.h"
15 #include "entry_p.h"
16 #include "fmt_p.h"
17 #include "field.h"
18 #include "field_p.h"
19 #include "parse.h"
20 #include "routines.h"
21 #include <string.h>
22 #include <errno.h>
23
24 typedef union uFmtSpec {
25 char *const_str;
26 struct {
27 fieldType ftype;
28 int width;
29 char *raw_fmtstr;
30 } field;
31 } fmtSpec;
32
33 struct sFmtElement {
34 union uFmtSpec spec;
35 int (* printer) (fmtSpec*, MIO* fp, const tagEntryInfo *);
36 struct sFmtElement *next;
37 };
38
printLiteral(fmtSpec * fspec,MIO * fp,const tagEntryInfo * tag CTAGS_ATTR_UNUSED)39 static int printLiteral (fmtSpec* fspec, MIO* fp, const tagEntryInfo * tag CTAGS_ATTR_UNUSED)
40 {
41 return mio_puts (fp, fspec->const_str);
42 }
43
isParserFieldCompatibleWithFtype(const tagField * pfield,int baseFtype)44 static bool isParserFieldCompatibleWithFtype (const tagField *pfield, int baseFtype)
45 {
46 do {
47 if (pfield->ftype == baseFtype)
48 return true;
49 baseFtype = nextSiblingField (baseFtype);
50 } while (baseFtype != FIELD_UNKNOWN);
51 return false;
52 }
53
printTagField(fmtSpec * fspec,MIO * fp,const tagEntryInfo * tag)54 static int printTagField (fmtSpec* fspec, MIO* fp, const tagEntryInfo * tag)
55 {
56 int i;
57 int width = fspec->field.width;
58 int ftype;
59 const char* str = NULL;
60
61 ftype = fspec->field.ftype;
62
63 if (isCommonField (ftype))
64 str = renderField (ftype, tag, NO_PARSER_FIELD);
65 else
66 {
67 unsigned int findex;
68 const tagField *f;
69
70 for (findex = 0; findex < tag->usedParserFields; findex++)
71 {
72 f = getParserFieldForIndex(tag, findex);
73 if (isParserFieldCompatibleWithFtype (f, ftype))
74 break;
75 }
76
77 if (findex == tag->usedParserFields)
78 str = "";
79 else if (isFieldEnabled (f->ftype))
80 {
81 unsigned int dt = getFieldDataType (f->ftype);
82 if (dt & FIELDTYPE_STRING)
83 {
84 str = renderField (f->ftype, tag, findex);
85 if ((dt & FIELDTYPE_BOOL) && str[0] == '\0')
86 {
87 /* TODO: FIELD_NULL_LETTER_STRING */
88 str = "-";
89 }
90 }
91 else if (dt & FIELDTYPE_BOOL)
92 str = getFieldName (f->ftype);
93 else
94 {
95 /* Not implemented */
96 AssertNotReached ();
97 str = "CTAGS INTERNAL BUG!";
98 }
99 }
100 }
101
102 if (str == NULL)
103 str = "";
104
105 if (width)
106 i = mio_printf (fp, fspec->field.raw_fmtstr, width, str);
107 else
108 {
109 mio_puts (fp, str);
110 i = strlen (str);
111 }
112 return i;
113 }
114
queueLiteral(fmtElement ** last,char * literal)115 static fmtElement** queueLiteral (fmtElement **last, char *literal)
116 {
117 fmtElement *cur = xMalloc (1, fmtElement);
118
119 cur->spec.const_str = literal;
120 cur->printer = printLiteral;
121 cur->next = NULL;
122 *last = cur;
123 return &(cur->next);
124 }
125
126 /* `getLanguageComponentInFieldName' is used as part of the option parameter
127 for --_xformat option.
128
129 It splits the value of fullName into a language part and a field name part.
130 Here the two parts are combined with `.'.
131
132 If it cannot find a period, it returns LANG_IGNORE and sets
133 fullname to *fieldName.
134
135 If lang part if `*', it returns LANG_AUTO and sets the field
136 name part to *fieldName.
137
138 Though a period is found but no parser (langType) is found for
139 the language parser, this function returns LANG_IGNORE and sets
140 NULL to *fieldName.
141
142 A proper parser is found, this function returns langType for the
143 parser and sets the field name part to *fieldName. */
getLanguageComponentInFieldName(const char * fullName,const char ** fieldName)144 static langType getLanguageComponentInFieldName (const char *fullName,
145 const char **fieldName)
146 {
147 const char *tmp;
148 langType language;
149
150 tmp = strchr (fullName, '.');
151 if (tmp)
152 {
153 size_t len = tmp - fullName;
154
155 if (len == 1 && fullName[0] == '*')
156 {
157 language = LANG_AUTO;
158 *fieldName = tmp + 1;
159 }
160 else if (len == 0)
161 {
162 language = LANG_IGNORE;
163 *fieldName = tmp + 1;
164 }
165 else
166 {
167 language = getNamedLanguage (fullName, len);
168 if (language == LANG_IGNORE)
169 *fieldName = NULL;
170 else
171 *fieldName = tmp + 1;
172 }
173 }
174 else
175 {
176 language = LANG_IGNORE;
177 *fieldName = fullName;
178 }
179 return language;
180 }
181
queueTagField(fmtElement ** last,long width,bool truncation,char field_letter,const char * field_name)182 static fmtElement** queueTagField (fmtElement **last, long width, bool truncation,
183 char field_letter, const char *field_name)
184 {
185 fieldType ftype;
186 fmtElement *cur;
187 langType language;
188
189 if (field_letter == NUL_FIELD_LETTER)
190 {
191 const char *f;
192
193 language = getLanguageComponentInFieldName (field_name, &f);
194 if (f == NULL)
195 error (FATAL, "No suitable parser for field name: %s", field_name);
196 ftype = getFieldTypeForNameAndLanguage (f, language);
197 }
198 else
199 {
200 language = LANG_IGNORE;
201 ftype = getFieldTypeForOption (field_letter);
202 }
203
204 if (ftype == FIELD_UNKNOWN)
205 {
206 if (field_letter == NUL_FIELD_LETTER)
207 error (FATAL, "No such field name: %s", field_name);
208 else
209 error (FATAL, "No such field letter: %c", field_letter);
210 }
211
212 if (!doesFieldHaveRenderer (ftype, false))
213 {
214 Assert (field_letter != NUL_FIELD_LETTER);
215 error (FATAL, "The field cannot be printed in format output: %c", field_letter);
216 }
217
218 cur = xMalloc (1, fmtElement);
219
220 cur->spec.field.width = width;
221 cur->spec.field.ftype = ftype;
222
223 if (width < 0)
224 {
225 cur->spec.field.width *= -1;
226 cur->spec.field.raw_fmtstr = (truncation? "%-.*s": "%-*s");
227 }
228 else if (width > 0)
229 cur->spec.field.raw_fmtstr = (truncation? "%.*s": "%*s");
230 else
231 cur->spec.field.raw_fmtstr = NULL;
232
233 enableField (ftype, true);
234 if (language == LANG_AUTO)
235 {
236 fieldType ftype_next = ftype;
237
238 while ((ftype_next = nextSiblingField (ftype_next)) != FIELD_UNKNOWN)
239 enableField (ftype_next, true);
240 }
241
242 cur->printer = printTagField;
243 cur->next = NULL;
244 *last = cur;
245 return &(cur->next);
246 }
247
fmtNew(const char * fmtString)248 extern fmtElement *fmtNew (const char* fmtString)
249 {
250 int i;
251 vString *literal = NULL;
252 fmtElement *code = NULL;
253 fmtElement **last = &code;
254 bool found_percent = false;
255 long column_width;
256 const char* cursor;
257
258 cursor = fmtString;
259
260 for (i = 0; cursor[i] != '\0'; ++i)
261 {
262 if (found_percent)
263 {
264 found_percent = false;
265 if (cursor[i] == '%')
266 {
267 if (literal == NULL)
268 literal = vStringNew ();
269 vStringPut (literal, cursor[i]);
270 }
271 else
272 {
273 int justification_right = 1;
274 bool truncation = false;
275 vString *width = NULL;
276 if (literal)
277 {
278 char* l = vStringDeleteUnwrap (literal);
279 literal = NULL;
280 last = queueLiteral (last, l);
281 }
282 if (cursor [i] == '-')
283 {
284 justification_right = -1;
285 i++;
286
287 if (cursor [i] == '\0')
288 error (FATAL, "unexpectedly terminated just after '-': \"%s\"", fmtString);
289
290 }
291 if (cursor [i] == '.')
292 {
293 truncation = true;
294 i++;
295
296 if (cursor [i] == '\0')
297 error (FATAL, "unexpectedly terminated just after '.': \"%s\"", fmtString);
298 }
299
300 while ( '0' <= cursor[i] && cursor[i] <= '9' )
301 {
302 if (width == NULL)
303 width = vStringNew ();
304 vStringPut (width, cursor[i]);
305 i++;
306
307 if (cursor [i] == '\0')
308 error (FATAL, "unexpectedly terminated during parsing column width: \"%s\"", fmtString);
309 }
310
311 if (justification_right == -1 && width == NULL)
312 error (FATAL, "no column width given after '-': \"%s\"", fmtString);
313
314 column_width = 0;
315 if (width)
316 {
317 if(!strToLong (vStringValue (width), 0, &column_width))
318 error (FATAL | PERROR, "converting failed: %s", vStringValue (width));
319 vStringDelete (width);
320 width = NULL;
321 column_width *= justification_right;
322 }
323
324 if (cursor[i] == '{')
325 {
326 vString *field_name = vStringNew ();
327
328 i++;
329 for (; cursor[i] != '}'; i++)
330 vStringPut (field_name, cursor[i]);
331
332 last = queueTagField (last, column_width, truncation,
333 NUL_FIELD_LETTER, vStringValue (field_name));
334
335 vStringDelete (field_name);
336 }
337 else
338 last = queueTagField (last, column_width, truncation,
339 cursor[i], NULL);
340 }
341
342 }
343 else
344 {
345 if (cursor[i] == '%')
346 found_percent = true;
347 else
348 {
349 if (literal == NULL)
350 literal = vStringNew ();
351
352 vStringPut (literal, cursor[i]);
353 }
354 }
355 }
356 if (literal)
357 {
358 char* l = vStringDeleteUnwrap (literal);
359 literal = NULL;
360 last = queueLiteral (last, l);
361 }
362 return code;
363 }
364
fmtPrint(fmtElement * fmtelts,MIO * fp,const tagEntryInfo * tag)365 extern int fmtPrint (fmtElement * fmtelts, MIO* fp, const tagEntryInfo *tag)
366 {
367 fmtElement *f = fmtelts;
368 int i = 0;
369 while (f)
370 {
371 i += f->printer (&(f->spec), fp, tag);
372 f = f->next;
373 }
374 return i;
375 }
376
fmtDelete(fmtElement * fmtelts)377 extern void fmtDelete (fmtElement * fmtelts)
378 {
379 fmtElement *f = fmtelts;
380 fmtElement *next;
381
382 while (f)
383 {
384 next = f->next;
385 if (f->printer == printLiteral)
386 {
387 eFree (f->spec.const_str);
388 f->spec.const_str = NULL;
389 }
390 f->next = NULL;
391 eFree (f);
392 f = next;
393 }
394 }
395