xref: /Universal-ctags/main/fmt.c (revision c0421c5ec596b5f2a3e1a16a61d6ee3368a68842)
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