1 /*
2 *	$Id: vim.c 485 2006-10-24 12:06:19Z dfishburn $
3 *
4 *	Copyright (c) 2000-2003, Darren Hiebert
5 *
6 *	This source code is released for free distribution under the terms of the
7 *	GNU General Public License.
8 *
9 *	Thanks are due to Jay Glanville for significant improvements.
10 *
11 *	This module contains functions for generating tags for user-defined
12 *	functions for the Vim editor.
13 */
14 
15 /*
16 *	INCLUDE FILES
17 */
18 #include "general.h"  /* must always come first */
19 
20 #include <string.h>
21 #include <setjmp.h>
22 #ifdef DEBUG
23 #include <stdio.h>
24 #endif
25 
26 
27 #include "parse.h"
28 #include "read.h"
29 #include "vstring.h"
30 
31 #if 0
32 typedef struct sLineInfo {
33 	tokenType	type;
34 	keywordId	keyword;
35 	vString *	string;
36 	vString *	scope;
37 	unsigned long lineNumber;
38 	fpos_t filePosition;
39 } lineInfo;
40 #endif
41 
42 /*
43 *	DATA DEFINITIONS
44 */
45 typedef enum {
46 	K_AUGROUP,
47 	K_COMMAND,
48 	K_FUNCTION,
49 	K_MAP,
50 	K_VARIABLE
51 } vimKind;
52 
53 static kindOption VimKinds [] = {
54 	{ TRUE,  'a', "augroup",  "autocommand groups" },
55 	{ TRUE,  'c', "command",  "user-defined commands" },
56 	{ TRUE,  'f', "function", "function definitions" },
57 	{ TRUE,  'm', "map",      "maps" },
58 	{ TRUE,  'v', "variable", "variable definitions" },
59 };
60 
61 /*
62  *	 DATA DECLARATIONS
63  */
64 
65 #if 0
66 typedef enum eException {
67 	ExceptionNone, ExceptionEOF
68 } exception_t;
69 #endif
70 
71 /*
72  *	DATA DEFINITIONS
73  */
74 
75 #if 0
76 static jmp_buf Exception;
77 #endif
78 
79 /*
80  *	FUNCTION DEFINITIONS
81  */
82 
83 /* This function takes a char pointer, tries to find a scope separator in the
84  * string, and if it does, returns a pointer to the character after the colon,
85  * and the character defining the scope.
86  * If a colon is not found, it returns the original pointer.
87  */
skipPrefix(const unsigned char * name,int * scope)88 static const unsigned char* skipPrefix (const unsigned char* name, int *scope)
89 {
90 	const unsigned char* result = name;
91 	int counter;
92 	size_t length;
93 	length = strlen((const char*)name);
94 	if (scope != NULL)
95 		*scope = '\0';
96 	if (length > 3 && name[1] == ':')
97 	{
98 		if (scope != NULL)
99 			*scope = *name;
100 		result = name + 2;
101 	}
102 	else if (length > 5 && strncasecmp ((const char*) name, "<SID>", (size_t) 5) == 0)
103 	{
104 		if (scope != NULL)
105 			*scope = *name;
106 		result = name + 5;
107 	}
108 	else
109 	{
110 		/*
111 		 * Vim7 check for dictionaries or autoload function names
112 		 */
113 		counter = 0;
114 		do
115 		{
116 			switch ( name[counter] )
117 			{
118 				case '.':
119 					/* Set the scope to d - Dictionary */
120 					*scope = 'd';
121 					break;
122 				case '#':
123 					/* Set the scope to a - autoload */
124 					*scope = 'a';
125 					break;
126 			}
127 			++counter;
128 		} while (isalnum ((int) name[counter]) ||
129 				name[counter] == '_'		   ||
130 				name[counter] == '.'		   ||
131 				name[counter] == '#'
132 				);
133 	}
134 	return result;
135 }
136 
isMap(const unsigned char * line)137 static boolean isMap (const unsigned char* line)
138 {
139 	/*
140 	 * There are many different short cuts for specifying a map.
141 	 * This routine should capture all the permutations.
142 	 */
143 	if (
144 			strncmp ((const char*) line, "map",      (size_t) 3) == 0 ||
145 			strncmp ((const char*) line, "nm",       (size_t) 2) == 0 ||
146 			strncmp ((const char*) line, "nma",      (size_t) 3) == 0 ||
147 			strncmp ((const char*) line, "nmap",     (size_t) 4) == 0 ||
148 			strncmp ((const char*) line, "vm",       (size_t) 2) == 0 ||
149 			strncmp ((const char*) line, "vma",      (size_t) 3) == 0 ||
150 			strncmp ((const char*) line, "vmap",     (size_t) 4) == 0 ||
151 			strncmp ((const char*) line, "om",       (size_t) 2) == 0 ||
152 			strncmp ((const char*) line, "oma",      (size_t) 3) == 0 ||
153 			strncmp ((const char*) line, "omap",     (size_t) 4) == 0 ||
154 			strncmp ((const char*) line, "im",       (size_t) 2) == 0 ||
155 			strncmp ((const char*) line, "ima",      (size_t) 3) == 0 ||
156 			strncmp ((const char*) line, "imap",     (size_t) 4) == 0 ||
157 			strncmp ((const char*) line, "lm",       (size_t) 2) == 0 ||
158 			strncmp ((const char*) line, "lma",      (size_t) 3) == 0 ||
159 			strncmp ((const char*) line, "lmap",     (size_t) 4) == 0 ||
160 			strncmp ((const char*) line, "cm",       (size_t) 2) == 0 ||
161 			strncmp ((const char*) line, "cma",      (size_t) 3) == 0 ||
162 			strncmp ((const char*) line, "cmap",     (size_t) 4) == 0 ||
163 			strncmp ((const char*) line, "no",       (size_t) 2) == 0 ||
164 			strncmp ((const char*) line, "nor",      (size_t) 3) == 0 ||
165 			strncmp ((const char*) line, "nore",     (size_t) 4) == 0 ||
166 			strncmp ((const char*) line, "norem",    (size_t) 5) == 0 ||
167 			strncmp ((const char*) line, "norema",   (size_t) 6) == 0 ||
168 			strncmp ((const char*) line, "noremap",  (size_t) 7) == 0 ||
169 			strncmp ((const char*) line, "nno",      (size_t) 3) == 0 ||
170 			strncmp ((const char*) line, "nnor",     (size_t) 4) == 0 ||
171 			strncmp ((const char*) line, "nnore",    (size_t) 5) == 0 ||
172 			strncmp ((const char*) line, "nnorem",   (size_t) 6) == 0 ||
173 			strncmp ((const char*) line, "nnorema",  (size_t) 7) == 0 ||
174 			strncmp ((const char*) line, "nnoremap", (size_t) 8) == 0 ||
175 			strncmp ((const char*) line, "vno",      (size_t) 3) == 0 ||
176 			strncmp ((const char*) line, "vnor",     (size_t) 4) == 0 ||
177 			strncmp ((const char*) line, "vnore",    (size_t) 5) == 0 ||
178 			strncmp ((const char*) line, "vnorem",   (size_t) 6) == 0 ||
179 			strncmp ((const char*) line, "vnorema",  (size_t) 7) == 0 ||
180 			strncmp ((const char*) line, "vnoremap", (size_t) 8) == 0 ||
181 			strncmp ((const char*) line, "ono",      (size_t) 3) == 0 ||
182 			strncmp ((const char*) line, "onor",     (size_t) 4) == 0 ||
183 			strncmp ((const char*) line, "onore",    (size_t) 5) == 0 ||
184 			strncmp ((const char*) line, "onorem",   (size_t) 6) == 0 ||
185 			strncmp ((const char*) line, "onorema",  (size_t) 7) == 0 ||
186 			strncmp ((const char*) line, "onoremap", (size_t) 8) == 0 ||
187 			strncmp ((const char*) line, "ino",      (size_t) 3) == 0 ||
188 			strncmp ((const char*) line, "inor",     (size_t) 4) == 0 ||
189 			strncmp ((const char*) line, "inore",    (size_t) 5) == 0 ||
190 			strncmp ((const char*) line, "inorem",   (size_t) 6) == 0 ||
191 			strncmp ((const char*) line, "inorema",  (size_t) 7) == 0 ||
192 			strncmp ((const char*) line, "inoremap", (size_t) 8) == 0 ||
193 			strncmp ((const char*) line, "lno",      (size_t) 3) == 0 ||
194 			strncmp ((const char*) line, "lnor",     (size_t) 4) == 0 ||
195 			strncmp ((const char*) line, "lnore",    (size_t) 5) == 0 ||
196 			strncmp ((const char*) line, "lnorem",   (size_t) 6) == 0 ||
197 			strncmp ((const char*) line, "lnorema",  (size_t) 7) == 0 ||
198 			strncmp ((const char*) line, "lnoremap", (size_t) 8) == 0 ||
199 			strncmp ((const char*) line, "cno",      (size_t) 3) == 0 ||
200 			strncmp ((const char*) line, "cnor",     (size_t) 4) == 0 ||
201 			strncmp ((const char*) line, "cnore",    (size_t) 5) == 0 ||
202 			strncmp ((const char*) line, "cnorem",   (size_t) 6) == 0 ||
203 			strncmp ((const char*) line, "cnorema",  (size_t) 7) == 0 ||
204 			strncmp ((const char*) line, "cnoremap", (size_t) 8) == 0
205 			)
206 			return TRUE;
207 
208 	return FALSE;
209 }
210 
readVimLine(void)211 static const unsigned char * readVimLine (void)
212 {
213 	const unsigned char *line;
214 
215 	while ((line = fileReadLine ()) != NULL)
216 	{
217 		while (isspace ((int) *line))
218 			++line;
219 
220 		if ((int) *line == '"')
221 			continue;  /* skip comment */
222 
223 		break;
224 	}
225 
226 	return line;
227 }
228 
parseFunction(const unsigned char * line)229 static void parseFunction (const unsigned char *line)
230 {
231 	vString *name = vStringNew ();
232 	/* boolean inFunction = FALSE; */
233 	int scope;
234 
235 	const unsigned char *cp = line + 1;
236 
237 	if ((int) *++cp == 'n'	&&	(int) *++cp == 'c'	&&
238 		(int) *++cp == 't'	&&	(int) *++cp == 'i'	&&
239 		(int) *++cp == 'o'	&&	(int) *++cp == 'n')
240 			++cp;
241 	if ((int) *cp == '!')
242 		++cp;
243 	if (isspace ((int) *cp))
244 	{
245 		while (*cp && isspace ((int) *cp))
246 			++cp;
247 
248 		if (*cp)
249 		{
250 			cp = skipPrefix (cp, &scope);
251 			if (isupper ((int) *cp)  ||
252 					scope == 's'  ||  /* script scope */
253 					scope == '<'  ||  /* script scope */
254 					scope == 'd'  ||  /* dictionary */
255 					scope == 'a')	  /* autoload */
256 			{
257 				do
258 				{
259 					vStringPut (name, (int) *cp);
260 					++cp;
261 				} while (isalnum ((int) *cp) ||  *cp == '_' ||	*cp == '.' ||  *cp == '#');
262 				vStringTerminate (name);
263 				makeSimpleTag (name, VimKinds, K_FUNCTION);
264 				vStringClear (name);
265 			}
266 		}
267 	}
268 
269 	/* TODO - update struct to indicate inside function */
270 	while ((line = readVimLine ()) != NULL)
271 	{
272 		/*
273 		 * Vim7 added the for/endfo[r] construct, so we must first
274 		 * check for an "endfo", before a "endf"
275 		 */
276 		if ( (!strncmp ((const char*) line, "endfo", (size_t) 5) == 0) &&
277 				(strncmp ((const char*) line, "endf", (size_t) 4) == 0)   )
278 			break;
279 		/* TODO - call parseVimLine */
280 	}
281 	vStringDelete (name);
282 }
283 
parseAutogroup(const unsigned char * line)284 static void parseAutogroup (const unsigned char *line)
285 {
286 	vString *name = vStringNew ();
287 
288 	/* Found Autocommand Group (augroup) */
289 	const unsigned char *cp = line + 2;
290 	if ((int) *++cp == 'r' && (int) *++cp == 'o' &&
291 			(int) *++cp == 'u' && (int) *++cp == 'p')
292 		++cp;
293 	if (isspace ((int) *cp))
294 	{
295 		while (*cp && isspace ((int) *cp))
296 			++cp;
297 
298 		if (*cp)
299 		{
300 			if (strncasecmp ((const char*) cp, "end", (size_t) 3) != 0)
301 			{
302 				do
303 				{
304 					vStringPut (name, (int) *cp);
305 					++cp;
306 				} while (isalnum ((int) *cp)  ||  *cp == '_');
307 				vStringTerminate (name);
308 				makeSimpleTag (name, VimKinds, K_AUGROUP);
309 				vStringClear (name);
310 			}
311 		}
312 	}
313 	vStringDelete (name);
314 }
315 
parseCommand(const unsigned char * line)316 static boolean parseCommand (const unsigned char *line)
317 {
318 	vString *name = vStringNew ();
319 	boolean cmdProcessed = TRUE;
320 
321 	/*
322 	 * Found a user-defined command
323 	 *
324 	 * They can have many options preceeded by a dash
325 	 * command! -nargs=+ -complete Select  :call s:DB_execSql("select " . <q-args>)
326 	 * The name of the command should be the first word not preceeded by a dash
327 	 *
328 	 */
329 	const unsigned char *cp = line;
330 
331 	if ( (int) *cp == '\\' )
332 	{
333 		/*
334 		 * We are recursively calling this function is the command
335 		 * has been continued on to the next line
336 		 *
337 		 * Vim statements can be continued onto a newline using a \
338 		 * to indicate the previous line is continuing.
339 		 *
340 		 * com -nargs=1 -bang -complete=customlist,EditFileComplete
341 		 * 			\ EditFile edit<bang> <args>
342 		 *
343 		 * If the following lines do not have a line continuation
344 		 * the command must not be spanning multiple lines and should
345 		 * be synatically incorrect.
346 		 */
347 		if ((int) *cp == '\\')
348 			++cp;
349 
350 		while (*cp && isspace ((int) *cp))
351 			++cp;
352 	}
353 	else if ( (!strncmp ((const char*) line, "comp", (size_t) 4) == 0) &&
354 		     (!strncmp ((const char*) line, "comc", (size_t) 4) == 0) &&
355 				(strncmp ((const char*) line, "com", (size_t) 3) == 0) )
356 	{
357 		cp += 2;
358 		if ((int) *++cp == 'm' && (int) *++cp == 'a' &&
359 				(int) *++cp == 'n' && (int) *++cp == 'd')
360 			++cp;
361 
362 		if ((int) *cp == '!')
363 			++cp;
364 
365 		while (*cp && isspace ((int) *cp))
366 			++cp;
367 	}
368 	else
369 	{
370 		/*
371 		 * We are recursively calling this function.  If it does not start
372 		 * with "com" or a line continuation character, we have moved off
373 		 * the command line and should let the other routines parse this file.
374 		 */
375 		cmdProcessed = FALSE;
376 		goto cleanUp;
377 	}
378 
379 	/*
380 	 * Strip off any spaces and options which are part of the command.
381 	 * These should preceed the command name.
382 	 */
383 	do
384 	{
385 		if (isspace ((int) *cp))
386 		{
387 			++cp;
388 		}
389 		else if (*cp == '-')
390 		{
391 			/*
392 			 * Read until the next space which sparates options or the name
393 			 */
394 			while (*cp && !isspace ((int) *cp))
395 				++cp;
396 		}
397 	} while ( *cp &&  !isalnum ((int) *cp) );
398 
399 	if ( ! *cp )
400 	{
401 		/*
402 		 * We have reached the end of the line without finding the command name.
403 		 * Read the next line and continue processing it as a command.
404 		 */
405 		line = readVimLine();
406 		parseCommand(line);
407 		goto cleanUp;
408 	}
409 
410 	do
411 	{
412 		vStringPut (name, (int) *cp);
413 		++cp;
414 	} while (isalnum ((int) *cp)  ||  *cp == '_');
415 
416 	vStringTerminate (name);
417 	makeSimpleTag (name, VimKinds, K_COMMAND);
418 	vStringClear (name);
419 
420 cleanUp:
421 	vStringDelete (name);
422 
423 	return cmdProcessed;
424 }
425 
parseLet(const unsigned char * line)426 static void parseLet (const unsigned char *line)
427 {
428 	vString *name = vStringNew ();
429 
430 	/* we've found a variable declared outside of a function!! */
431 	const unsigned char *cp = line + 3;
432 	const unsigned char *np = line;
433 	/* get the name */
434 	if (isspace ((int) *cp))
435 	{
436 		while (*cp && isspace ((int) *cp))
437 			++cp;
438 
439 		/*
440 		 * Ignore lets which set:
441 		 *    &  - local buffer vim settings
442 		 *    @  - registers
443 		 *    [  - Lists or Dictionaries
444 		 */
445 		if (!*cp || *cp == '&' || *cp == '@' || *cp == '[' )
446 			goto cleanUp;
447 
448 		/*
449 		 * Ignore vim variables which are read only
450 		 *    v: - Vim variables.
451 		 */
452 		np = cp;
453 		++np;
454 		if ((int) *cp == 'v' && (int) *np == ':' )
455 			goto cleanUp;
456 
457 		/* deal with spaces, $, @ and & */
458 		while (*cp && *cp != '$' && !isalnum ((int) *cp))
459 			++cp;
460 
461 		if (!*cp)
462 			goto cleanUp;
463 
464 		/* cp = skipPrefix (cp, &scope); */
465 		do
466 		{
467 			if (!*cp)
468 				break;
469 
470 			vStringPut (name, (int) *cp);
471 			++cp;
472 		} while (isalnum ((int) *cp)  ||  *cp == '_'  ||  *cp == '#'  ||  *cp == ':'  ||  *cp == '$');
473 		vStringTerminate (name);
474 		makeSimpleTag (name, VimKinds, K_VARIABLE);
475 		vStringClear (name);
476 	}
477 
478 cleanUp:
479 	vStringDelete (name);
480 }
481 
parseMap(const unsigned char * line)482 static boolean parseMap (const unsigned char *line)
483 {
484 	vString *name = vStringNew ();
485 
486 	const unsigned char *cp = line;
487 
488 	/* Remove map */
489 	while (*cp && isalnum ((int) *cp))
490 		++cp;
491 
492 	if ((int) *cp == '!')
493 		++cp;
494 
495 	/*
496 	 * Maps follow this basic format
497 	 *     map
498      *    nnoremap <silent> <F8> :Tlist<CR>
499      *    map <unique> <Leader>scdt <Plug>GetColumnDataType
500      *    inoremap ,,, <esc>diwi<<esc>pa><cr></<esc>pa><esc>kA
501      *    inoremap <buffer> ( <C-R>=PreviewFunctionSignature()<LF>
502 	 *
503 	 * The Vim help shows the various special arguments available to a map:
504 	 * 1.2 SPECIAL ARGUMENTS					*:map-arguments*
505      *    <buffer>
506 	 *    <silent>
507 	 *    <script>
508 	 *    <unique>
509 	 *    <special>
510 	 *    <expr>
511 	 *
512 	 * Strip the special arguments from the map command, this should leave
513 	 * the map name which we will use as the "name".
514 	 */
515 
516 	do
517 	{
518 		while (*cp && isspace ((int) *cp))
519 			++cp;
520 
521 		if (strncmp ((const char*) cp, "<Leader>", (size_t) 8) == 0)
522 			break;
523 
524 		if (
525 				strncmp ((const char*) cp, "<buffer>", (size_t) 8) == 0 ||
526 				strncmp ((const char*) cp, "<silent>", (size_t) 8) == 0 ||
527 				strncmp ((const char*) cp, "<script>", (size_t) 8) == 0 ||
528 				strncmp ((const char*) cp, "<unique>", (size_t) 8) == 0
529 		   )
530 		{
531 			cp += 8;
532 			continue;
533 		}
534 
535 		if (strncmp ((const char*) cp, "<expr>", (size_t) 6) == 0)
536 		{
537 			cp += 6;
538 			continue;
539 		}
540 
541 		if (strncmp ((const char*) cp, "<special>", (size_t) 9) == 0)
542 		{
543 			cp += 9;
544 			continue;
545 		}
546 
547 		break;
548 	} while (*cp);
549 
550 	do
551 	{
552 		vStringPut (name, (int) *cp);
553 		++cp;
554 	} while (*cp && *cp != ' ');
555 
556 	vStringTerminate (name);
557 	makeSimpleTag (name, VimKinds, K_MAP);
558 	vStringClear (name);
559 
560 	vStringDelete (name);
561 
562 	return TRUE;
563 }
564 
parseVimLine(const unsigned char * line)565 static boolean parseVimLine (const unsigned char *line)
566 {
567 	boolean readNextLine = TRUE;
568 
569 	if ( (!strncmp ((const char*) line, "comp", (size_t) 4) == 0) &&
570 			(!strncmp ((const char*) line, "comc", (size_t) 4) == 0) &&
571 			(strncmp ((const char*) line, "com", (size_t) 3) == 0) )
572 	{
573 		readNextLine = parseCommand(line);
574 		/* TODO - Handle parseCommand returning FALSE */
575 	}
576 
577 	if (isMap(line))
578 	{
579 		parseMap(line);
580 	}
581 
582 	if (strncmp ((const char*) line, "fu", (size_t) 2) == 0)
583 	{
584 		parseFunction(line);
585 	}
586 
587 	if	(strncmp ((const char*) line, "aug", (size_t) 3) == 0)
588 	{
589 		parseAutogroup(line);
590 	}
591 
592 	if ( strncmp ((const char*) line, "let", (size_t) 3) == 0 )
593 	{
594 		parseLet(line);
595 	}
596 
597 	return readNextLine;
598 }
599 
parseVimFile(const unsigned char * line)600 static void parseVimFile (const unsigned char *line)
601 {
602 	boolean readNextLine = TRUE;
603 	line = readVimLine();
604 
605 	while (line != NULL)
606 	{
607 		readNextLine = parseVimLine(line);
608 
609 		if ( readNextLine )
610 			line = readVimLine();
611 
612 	}
613 }
614 
findVimTags(void)615 static void findVimTags (void)
616 {
617 	const unsigned char *line;
618 		/* TODO - change this into a structure */
619 
620 	line = '\0';
621 
622 	parseVimFile (line);
623 }
624 
VimParser(void)625 extern parserDefinition* VimParser (void)
626 {
627 	static const char *const extensions [] = { "vim", NULL };
628 	parserDefinition* def = parserNew ("Vim");
629 	def->kinds		= VimKinds;
630 	def->kindCount	= KIND_COUNT (VimKinds);
631 	def->extensions = extensions;
632 	def->parser		= findVimTags;
633 	return def;
634 }
635 
636 /* vi:set tabstop=4 shiftwidth=4 noexpandtab: */
637