xref: /Universal-ctags/parsers/erlang.c (revision 0d502ef01a06f2d53250c160a71a28508c360d68)
1 /*
2 *   Copyright (c) 2003, Brent Fulgham <bfulgham@debian.org>
3 *
4 *   This source code is released for free distribution under the terms of the
5 *   GNU General Public License version 2 or (at your option) any later version.
6 *
7 *   This module contains functions for generating tags for Erlang language
8 *   files.  Some of the parsing constructs are based on the Emacs 'etags'
9 *   program by Francesco Potori <pot@gnu.org>
10 */
11 /*
12 *   INCLUDE FILES
13 */
14 #include "general.h"  /* must always come first */
15 
16 #include <string.h>
17 
18 #include "entry.h"
19 #include "read.h"
20 #include "parse.h"
21 #include "routines.h"
22 #include "vstring.h"
23 
24 /*
25 *   DATA DEFINITIONS
26 */
27 typedef enum {
28 	K_MACRO, K_FUNCTION, K_MODULE, K_RECORD, K_TYPE
29 } erlangKind;
30 
31 static kindDefinition ErlangKinds[] = {
32 	{true, 'd', "macro",    "macro definitions"},
33 	{true, 'f', "function", "functions"},
34 	{true, 'm', "module",   "modules"},
35 	{true, 'r', "record",   "record definitions"},
36 	{true, 't', "type",     "type definitions"},
37 };
38 
39 /*
40 *   FUNCTION DEFINITIONS
41 */
42 /* tagEntryInfo and vString should be preinitialized/preallocated but not
43  * necessary. If successful you will find class name in vString
44  */
45 
isIdentifierFirstCharacter(int c)46 static bool isIdentifierFirstCharacter (int c)
47 {
48 	return (bool) (isalpha (c));
49 }
50 
isIdentifierCharacter(int c)51 static bool isIdentifierCharacter (int c)
52 {
53 	return (bool) (isalnum (c) || c == '_' || c == ':');
54 }
55 
skipSpace(const unsigned char * cp)56 static const unsigned char *skipSpace (const unsigned char *cp)
57 {
58 	while (isspace ((int) *cp))
59 		++cp;
60 	return cp;
61 }
62 
parseIdentifier(const unsigned char * cp,vString * const identifier)63 static const unsigned char *parseIdentifier (
64 		const unsigned char *cp, vString *const identifier)
65 {
66 	vStringClear (identifier);
67 	while (isIdentifierCharacter ((int) *cp))
68 	{
69 		vStringPut (identifier, (int) *cp);
70 		++cp;
71 	}
72 	return cp;
73 }
74 
makeMemberTag(vString * const identifier,erlangKind kind,vString * const module)75 static void makeMemberTag (
76 		vString *const identifier, erlangKind kind, vString *const module)
77 {
78 	if (ErlangKinds [kind].enabled  &&  vStringLength (identifier) > 0)
79 	{
80 		tagEntryInfo tag;
81 		initTagEntry (&tag, vStringValue (identifier), kind);
82 
83 		if (module != NULL  &&  vStringLength (module) > 0)
84 		{
85 			tag.extensionFields.scopeKindIndex = K_MODULE;
86 			tag.extensionFields.scopeName = vStringValue (module);
87 		}
88 		makeTagEntry (&tag);
89 	}
90 }
91 
parseModuleTag(const unsigned char * cp,vString * const module)92 static void parseModuleTag (const unsigned char *cp, vString *const module)
93 {
94 	vString *const identifier = vStringNew ();
95 	parseIdentifier (cp, identifier);
96 	makeSimpleTag (identifier, K_MODULE);
97 
98 	/* All further entries go in the new module */
99 	vStringCopy (module, identifier);
100 	vStringDelete (identifier);
101 }
102 
parseSimpleTag(const unsigned char * cp,erlangKind kind)103 static void parseSimpleTag (const unsigned char *cp, erlangKind kind)
104 {
105 	vString *const identifier = vStringNew ();
106 	parseIdentifier (cp, identifier);
107 	makeSimpleTag (identifier, kind);
108 	vStringDelete (identifier);
109 }
110 
parseFunctionTag(const unsigned char * cp,vString * const module)111 static void parseFunctionTag (const unsigned char *cp, vString *const module)
112 {
113 	vString *const identifier = vStringNew ();
114 	parseIdentifier (cp, identifier);
115 	makeMemberTag (identifier, K_FUNCTION, module);
116 	vStringDelete (identifier);
117 }
118 
119 /*
120  * Directives are of the form:
121  * -module(foo)
122  * -define(foo, bar)
123  * -record(graph, {vtab = notable, cyclic = true}).
124  * -type some_type() :: any().
125  * -opaque some_opaque_type() :: any().
126  */
parseDirective(const unsigned char * cp,vString * const module)127 static void parseDirective (const unsigned char *cp, vString *const module)
128 {
129 	/*
130 	 * A directive will be either a record definition or a directive.
131 	 * Record definitions are handled separately
132 	 */
133 	vString *const directive = vStringNew ();
134 	const char *const drtv = vStringValue (directive);
135 	cp = parseIdentifier (cp, directive);
136 	cp = skipSpace (cp);
137 	if (*cp == '(')
138 		++cp;
139 
140 	if (strcmp (drtv, "record") == 0)
141 		parseSimpleTag (cp, K_RECORD);
142 	else if (strcmp (drtv, "define") == 0)
143 		parseSimpleTag (cp, K_MACRO);
144 	else if (strcmp (drtv, "type") == 0)
145 		parseSimpleTag (cp, K_TYPE);
146 	else if (strcmp (drtv, "opaque") == 0)
147 		parseSimpleTag (cp, K_TYPE);
148 	else if (strcmp (drtv, "module") == 0)
149 		parseModuleTag (cp, module);
150 	/* Otherwise, it was an import, export, etc. */
151 
152 	vStringDelete (directive);
153 }
154 
findErlangTags(void)155 static void findErlangTags (void)
156 {
157 	vString *const module = vStringNew ();
158 	const unsigned char *line;
159 
160 	while ((line = readLineFromInputFile ()) != NULL)
161 	{
162 		const unsigned char *cp = line;
163 
164 		if (*cp == '%')  /* skip initial comment */
165 			continue;
166 		if (*cp == '"')  /* strings sometimes start in column one */
167 			continue;
168 
169 		if ( *cp == '-')
170 		{
171 			++cp;  /* Move off of the '-' */
172 			parseDirective(cp, module);
173 		}
174 		else if (isIdentifierFirstCharacter ((int) *cp))
175 			parseFunctionTag (cp, module);
176 	}
177 	vStringDelete (module);
178 }
179 
ErlangParser(void)180 extern parserDefinition *ErlangParser (void)
181 {
182 	static const char *const extensions[] = { "erl", "ERL", "hrl", "HRL", NULL };
183 	parserDefinition *def = parserNew ("Erlang");
184 	def->kindTable = ErlangKinds;
185 	def->kindCount = ARRAY_SIZE (ErlangKinds);
186 	def->extensions = extensions;
187 	def->parser = findErlangTags;
188 	return def;
189 }
190