Rename fileUngetc() to ungetcToInputFile()
[geany-mirror.git] / ctags / parsers / css.c
blobb843076923df35ff765ce9670eb0d0a0385a2570
1 /***************************************************************************
2 * css.c
3 * Token-based parser for CSS definitions
4 * Author - Colomban Wendling <colomban@geany.org>
5 * License GPL-2
6 **************************************************************************/
7 #include "general.h"
9 #include <string.h>
10 #include <ctype.h>
12 #include "entry.h"
13 #include "parse.h"
14 #include "read.h"
15 #include "routines.h"
17 typedef enum eCssKinds {
18 K_CLASS, K_SELECTOR, K_ID
19 } cssKind;
21 static kindOption CssKinds [] = {
22 { TRUE, 'c', "class", "classes" },
23 { TRUE, 's', "selector", "selectors" },
24 { TRUE, 'i', "id", "identities" }
27 typedef enum {
28 /* any ASCII */
29 TOKEN_EOF = 257,
30 TOKEN_SELECTOR,
31 TOKEN_STRING
32 } tokenType;
34 typedef struct {
35 tokenType type;
36 vString *string;
37 } tokenInfo;
40 static boolean isSelectorChar (const int c)
42 /* attribute selectors are handled separately */
43 return (isalnum (c) ||
44 c == '_' || // allowed char
45 c == '-' || // allowed char
46 c == '+' || // allow all sibling in a single tag
47 c == '>' || // allow all child in a single tag
48 c == '|' || // allow namespace separator
49 c == '(' || // allow pseudo-class arguments
50 c == ')' ||
51 c == '.' || // allow classes and selectors
52 c == ':' || // allow pseudo classes
53 c == '*' || // allow globs as P + *
54 c == '#'); // allow ids
57 static void parseSelector (vString *const string, const int firstChar)
59 int c = firstChar;
62 vStringPut (string, (char) c);
63 c = getcFromInputFile ();
64 } while (isSelectorChar (c));
65 ungetcToInputFile (c);
66 vStringTerminate (string);
69 static void readToken (tokenInfo *const token)
71 int c;
73 vStringClear (token->string);
75 getNextChar:
77 c = getcFromInputFile ();
78 while (isspace (c))
79 c = getcFromInputFile ();
81 token->type = c;
82 switch (c)
84 case EOF: token->type = TOKEN_EOF; break;
86 case '\'':
87 case '"':
89 const int delimiter = c;
92 vStringPut (token->string, c);
93 c = getcFromInputFile ();
94 if (c == '\\')
95 c = getcFromInputFile ();
97 while (c != EOF && c != delimiter);
98 if (c != EOF)
99 vStringPut (token->string, c);
100 token->type = TOKEN_STRING;
101 break;
104 case '/': /* maybe comment start */
106 int d = getcFromInputFile ();
107 if (d != '*')
109 ungetcToInputFile (d);
110 vStringPut (token->string, c);
111 token->type = c;
113 else
115 d = getcFromInputFile ();
118 c = d;
119 d = getcFromInputFile ();
121 while (d != EOF && ! (c == '*' && d == '/'));
122 goto getNextChar;
124 break;
127 default:
128 if (! isSelectorChar (c))
130 vStringPut (token->string, c);
131 token->type = c;
133 else
135 parseSelector (token->string, c);
136 token->type = TOKEN_SELECTOR;
138 break;
142 /* sets selector kind in @p kind if found, otherwise don't touches @p kind */
143 static cssKind classifySelector (const vString *const selector)
145 size_t i;
147 for (i = vStringLength (selector); i > 0; --i)
149 char c = vStringItem (selector, i - 1);
150 if (c == '.')
151 return K_CLASS;
152 else if (c == '#')
153 return K_ID;
155 return K_SELECTOR;
158 static void findCssTags (void)
160 boolean readNextToken = TRUE;
161 tokenInfo token;
163 token.string = vStringNew ();
167 if (readNextToken)
168 readToken (&token);
170 readNextToken = TRUE;
172 if (token.type == '@')
173 { /* At-rules, from the "@" to the next block or semicolon */
174 boolean useContents;
175 readToken (&token);
176 useContents = (strcmp (vStringValue (token.string), "media") == 0 ||
177 strcmp (vStringValue (token.string), "supports") == 0);
178 while (token.type != TOKEN_EOF &&
179 token.type != ';' && token.type != '{')
181 readToken (&token);
183 /* HACK: we *eat* the opening '{' for medias and the like so that
184 * the content is parsed as if it was at the root */
185 readNextToken = useContents && token.type == '{';
187 else if (token.type == TOKEN_SELECTOR)
188 { /* collect selectors and make a tag */
189 cssKind kind = K_SELECTOR;
190 MIOPos filePosition;
191 unsigned long lineNumber;
192 vString *selector = vStringNew ();
195 if (vStringLength (selector) > 0)
196 vStringPut (selector, ' ');
197 vStringCat (selector, token.string);
199 kind = classifySelector (token.string);
200 lineNumber = getSourceLineNumber ();
201 filePosition = getInputFilePosition ();
203 readToken (&token);
205 /* handle attribute selectors */
206 if (token.type == '[')
208 int depth = 1;
209 while (depth > 0 && token.type != TOKEN_EOF)
211 vStringCat (selector, token.string);
212 readToken (&token);
213 if (token.type == '[')
214 depth++;
215 else if (token.type == ']')
216 depth--;
218 if (token.type != TOKEN_EOF)
219 vStringCat (selector, token.string);
220 readToken (&token);
223 while (token.type == TOKEN_SELECTOR);
224 /* we already consumed the next token, don't read it twice */
225 readNextToken = FALSE;
227 vStringTerminate (selector);
228 if (CssKinds[kind].enabled)
230 tagEntryInfo e;
231 initTagEntry (&e, vStringValue (selector));
233 e.lineNumber = lineNumber;
234 e.filePosition = filePosition;
235 e.kindName = CssKinds[kind].name;
236 e.kind = (char) CssKinds[kind].letter;
238 makeTagEntry (&e);
240 vStringDelete (selector);
242 else if (token.type == '{')
243 { /* skip over { ... } */
244 int depth = 1;
245 while (depth > 0 && token.type != TOKEN_EOF)
247 readToken (&token);
248 if (token.type == '{')
249 depth++;
250 else if (token.type == '}')
251 depth--;
255 while (token.type != TOKEN_EOF);
257 vStringDelete (token.string);
260 /* parser definition */
261 extern parserDefinition* CssParser (void)
263 static const char *const extensions [] = { "css", NULL };
264 parserDefinition* def = parserNew ("CSS");
265 def->kinds = CssKinds;
266 def->kindCount = ARRAY_SIZE (CssKinds);
267 def->extensions = extensions;
268 def->parser = findCssTags;
269 return def;