JWM Source Documentation
lex.c
Go to the documentation of this file.
1 
10 #include "jwm.h"
11 #include "lex.h"
12 #include "error.h"
13 #include "misc.h"
14 
16 static const int BLOCK_SIZE = 16;
17 
21 static const StringMappingType TOKEN_MAP[] = {
22  { "Active", TOK_ACTIVE },
23  { "Background", TOK_BACKGROUND },
24  { "Button", TOK_BUTTON },
25  { "ButtonClose", TOK_BUTTONCLOSE },
26  { "ButtonMax", TOK_BUTTONMAX },
27  { "ButtonMaxActive", TOK_BUTTONMAXACTIVE },
28  { "ButtonMenu", TOK_BUTTONMENU },
29  { "ButtonMin", TOK_BUTTONMIN },
30  { "Class", TOK_CLASS },
31  { "Clock", TOK_CLOCK },
32  { "ClockStyle", TOK_CLOCKSTYLE },
33  { "Close", TOK_CLOSE },
34  { "Corner", TOK_CORNER },
35  { "DefaultIcon", TOK_DEFAULTICON },
36  { "Desktop", TOK_DESKTOP },
37  { "Desktops", TOK_DESKTOPS },
38  { "Dock", TOK_DOCK },
39  { "DoubleClickDelta", TOK_DOUBLECLICKDELTA },
40  { "DoubleClickSpeed", TOK_DOUBLECLICKSPEED },
41  { "Dynamic", TOK_DYNAMIC },
42  { "Exit", TOK_EXIT },
43  { "FocusModel", TOK_FOCUSMODEL },
44  { "Font", TOK_FONT },
45  { "Foreground", TOK_FOREGROUND },
46  { "Group", TOK_GROUP },
47  { "Height", TOK_HEIGHT },
48  { "IconPath", TOK_ICONPATH },
49  { "Include", TOK_INCLUDE },
50  { "JWM", TOK_JWM },
51  { "Key", TOK_KEY },
52  { "Kill", TOK_KILL },
53  { "Layer", TOK_LAYER },
54  { "Maximize", TOK_MAXIMIZE },
55  { "Menu", TOK_MENU },
56  { "MenuStyle", TOK_MENUSTYLE },
57  { "Minimize", TOK_MINIMIZE },
58  { "Move", TOK_MOVE },
59  { "MoveMode", TOK_MOVEMODE },
60  { "Name", TOK_NAME },
61  { "Opacity", TOK_OPACITY },
62  { "Option", TOK_OPTION },
63  { "Outline", TOK_OUTLINE },
64  { "Pager", TOK_PAGER },
65  { "PagerStyle", TOK_PAGERSTYLE },
66  { "Popup", TOK_POPUP },
67  { "PopupStyle", TOK_POPUPSTYLE },
68  { "Program", TOK_PROGRAM },
69  { "Resize", TOK_RESIZE },
70  { "ResizeMode", TOK_RESIZEMODE },
71  { "Restart", TOK_RESTART },
72  { "RestartCommand", TOK_RESTARTCOMMAND },
73  { "RootMenu", TOK_ROOTMENU },
74  { "SendTo", TOK_SENDTO },
75  { "Separator", TOK_SEPARATOR },
76  { "Shade", TOK_SHADE },
77  { "ShutdownCommand", TOK_SHUTDOWNCOMMAND },
78  { "SnapMode", TOK_SNAPMODE },
79  { "Spacer", TOK_SPACER },
80  { "StartupCommand", TOK_STARTUPCOMMAND },
81  { "Stick", TOK_STICK },
82  { "Swallow", TOK_SWALLOW },
83  { "TaskList", TOK_TASKLIST },
84  { "TaskListStyle", TOK_TASKLISTSTYLE },
85  { "Text", TOK_TEXT },
86  { "Tray", TOK_TRAY },
87  { "TrayButton", TOK_TRAYBUTTON },
88  { "TrayButtonStyle", TOK_TRAYBUTTONSTYLE },
89  { "TrayStyle", TOK_TRAYSTYLE },
90  { "Width", TOK_WIDTH },
91  { "WindowStyle", TOK_WINDOWSTYLE }
92 };
93 static const unsigned int TOKEN_MAP_COUNT
94  = sizeof(TOKEN_MAP) / sizeof(TOKEN_MAP[0]);
95 
96 static TokenNode *head;
97 
98 static TokenNode *CreateNode(TokenNode *current,
99  const char *file,
100  unsigned int line);
102 
103 static char IsElementEnd(char ch);
104 static char IsValueEnd(char ch);
105 static char IsAttributeEnd(char ch);
106 static char *ReadElementName(const char *line);
107 static char *ReadValue(const char *line,
108  const char *file,
109  char (*IsEnd)(char),
110  unsigned int *offset,
111  unsigned int *lineNumber);
112 static char *ReadElementValue(const char *line,
113  const char *file,
114  unsigned int *offset,
115  unsigned int *lineNumber);
116 static char *ReadAttributeValue(const char *line,
117  const char *file,
118  unsigned int *offset,
119  unsigned int *lineNumber);
120 static int ParseEntity(const char *entity, char *ch,
121  const char *file, unsigned int line);
122 static TokenType LookupType(const char *name, TokenNode *np);
123 
125 TokenNode *Tokenize(const char *line, const char *fileName)
126 {
127  AttributeNode *ap;
128  TokenNode *current;
129  char *temp;
130  unsigned int x;
131  unsigned int offset;
132  unsigned int lineNumber;
133  char inElement;
134  char found;
135 
136  head = NULL;
137  current = NULL;
138  inElement = 0;
139  lineNumber = 1;
140 
141  x = 0;
142  /* Skip any initial white space. */
143  while(IsSpace(line[x], &lineNumber)) {
144  x += 1;
145  }
146 
147  /* Skip any XML stuff. */
148  if(!strncmp(line + x, "<?", 2)) {
149  while(line[x]) {
150  if(line[x] == '\n') {
151  lineNumber += 1;
152  }
153  if(!strncmp(line + x, "?>", 2)) {
154  x += 2;
155  break;
156  }
157  x += 1;
158  }
159  }
160 
161  /* Process the XML data. */
162  while(line[x]) {
163 
164  /* Skip comments and white space. */
165  do {
166 
167  /* Skip white space. */
168  while(IsSpace(line[x], &lineNumber)) {
169  x += 1;
170  }
171 
172  /* Skip comments */
173  found = 0;
174  if(!strncmp(line + x, "<!--", 4)) {
175  while(line[x]) {
176  if(line[x] == '\n') {
177  lineNumber += 1;
178  }
179  if(!strncmp(line + x, "-->", 3)) {
180  x += 3;
181  found = 1;
182  break;
183  }
184  x += 1;
185  }
186  }
187 
188  } while(found);
189 
190  switch(line[x]) {
191  case '<':
192  x += 1;
193  if(line[x] == '/') {
194 
195  /* Close tag. */
196  x += 1;
197  temp = ReadElementName(line + x);
198  if(current) {
199  if(JLIKELY(temp)) {
200  if(JUNLIKELY(current->type != LookupType(temp, NULL))) {
201  Warning(_("%s[%u]: close tag \"%s\" does not "
202  "match open tag \"%s\""),
203  fileName, lineNumber, temp,
204  GetTokenName(current));
205  }
206  } else {
207  Warning(_("%s[%u]: unexpected and invalid close tag"),
208  fileName, lineNumber);
209  }
210  current = current->parent;
211  } else {
212  if(temp) {
213  Warning(_("%s[%u]: close tag \"%s\" without open tag"),
214  fileName, lineNumber, temp);
215  } else {
216  Warning(_("%s[%u]: invalid close tag"), fileName, lineNumber);
217  }
218  }
219  if(temp) {
220  x += strlen(temp);
221  Release(temp);
222  }
223 
224  } else {
225 
226  /* Open tag. */
227  current = CreateNode(current, fileName, lineNumber);
228  temp = ReadElementName(line + x);
229  if(JLIKELY(temp)) {
230  x += strlen(temp);
231  LookupType(temp, current);
232  Release(temp);
233  } else {
234  Warning(_("%s[%u]: invalid open tag"), fileName, lineNumber);
235  }
236 
237  }
238  inElement = 1;
239  break;
240  case '/':
241 
242  /* End of open/close tag. */
243  if(inElement) {
244  x += 1;
245  if(JLIKELY(line[x] == '>' && current)) {
246  x += 1;
247  current = current->parent;
248  inElement = 0;
249  } else {
250  Warning(_("%s[%u]: invalid tag"), fileName, lineNumber);
251  }
252  } else {
253  goto ReadDefault;
254  }
255 
256  break;
257  case '>':
258 
259  /* End of open tag. */
260  x += 1;
261  inElement = 0;
262  break;
263 
264  default:
265 ReadDefault:
266  if(inElement) {
267 
268  /* In the open tag; read attributes. */
269  if(current) {
270  ap = CreateAttribute(current);
271  ap->name = ReadElementName(line + x);
272  if(ap->name) {
273  x += strlen(ap->name);
274  if(line[x] == '=') {
275  x += 1;
276  }
277  if(line[x] == '\"') {
278  x += 1;
279  }
280  ap->value = ReadAttributeValue(line + x, fileName,
281  &offset, &lineNumber);
282  x += offset;
283  if(line[x] == '\"') {
284  x += 1;
285  }
286  }
287  }
288 
289  } else {
290 
291  /* In tag body; read text. */
292  temp = ReadElementValue(line + x, fileName, &offset, &lineNumber);
293  x += offset;
294  if(temp) {
295  if(current) {
296  if(current->value) {
297  const unsigned value_len = strlen(current->value);
298  const unsigned temp_len = strlen(temp);
299  current->value = Reallocate(current->value,
300  value_len + temp_len + 1);
301  memcpy(&current->value[value_len], temp, temp_len + 1);
302  Release(temp);
303  } else {
304  current->value = temp;
305  }
306  } else {
307  if(JUNLIKELY(temp[0])) {
308  Warning(_("%s[%u]: unexpected text: \"%s\""),
309  fileName, lineNumber, temp);
310  }
311  Release(temp);
312  }
313  }
314  }
315  break;
316  }
317  }
318 
319  return head;
320 }
321 
326 int ParseEntity(const char *entity, char *ch, const char *file,
327  unsigned int line)
328 {
329  char *temp;
330 
331  if(!strncmp("&quot;", entity, 6)) {
332  *ch = '\"';
333  return 6;
334  } else if(!strncmp("&lt;", entity, 4)) {
335  *ch = '<';
336  return 4;
337  } else if(!strncmp("&gt;", entity, 4)) {
338  *ch = '>';
339  return 4;
340  } else if(!strncmp("&amp;", entity, 5)) {
341  *ch = '&';
342  return 5;
343  } else if(!strncmp("&apos;", entity, 6)) {
344  *ch = '\'';
345  return 6;
346  } else if(!strncmp("&NewLine;", entity, 9)) {
347  *ch = '\n';
348  return 9;
349  } else {
350  unsigned int x;
351  for(x = 0; entity[x]; x++) {
352  if(entity[x] == ';') {
353  break;
354  }
355  }
356  if(entity[1] == '#' && entity[x] == ';') {
357  if(entity[2] == 'x') {
358  *ch = (char)strtol(&entity[3], NULL, 16);
359  } else {
360  *ch = (char)strtol(&entity[2], NULL, 10);
361  }
362  return x + 1;
363  } else {
364  temp = AllocateStack(x + 2);
365  strncpy(temp, entity, x + 1);
366  temp[x + 1] = 0;
367  Warning(_("%s[%d]: invalid entity: \"%.8s\""), file, line, temp);
368  ReleaseStack(temp);
369  *ch = '&';
370  return 1;
371  }
372  }
373 }
374 
376 char IsElementEnd(char ch)
377 {
378  switch(ch) {
379  case ' ':
380  case '\t':
381  case '\n':
382  case '\r':
383  case '\"':
384  case '>':
385  case '<':
386  case '/':
387  case '=':
388  case 0:
389  return 1;
390  default:
391  return 0;
392  }
393 }
394 
396 char IsAttributeEnd(char ch)
397 {
398  switch(ch) {
399  case 0:
400  case '\"':
401  return 1;
402  default:
403  return 0;
404  }
405 }
406 
408 char IsValueEnd(char ch)
409 {
410  switch(ch) {
411  case 0:
412  case '<':
413  return 1;
414  default:
415  return 0;
416  }
417 }
418 
420 char *ReadElementName(const char *line)
421 {
422 
423  char *buffer;
424  unsigned int len;
425 
426  /* Get the length of the element. */
427  for(len = 0; !IsElementEnd(line[len]); len++);
428 
429  /* Allocate space for the element. */
430  buffer = Allocate(len + 1);
431  memcpy(buffer, line, len);
432  buffer[len] = 0;
433 
434  return buffer;
435 
436 }
437 
439 char *ReadValue(const char *line,
440  const char *file,
441  char (*IsEnd)(char),
442  unsigned int *offset,
443  unsigned int *lineNumber)
444 {
445  char *buffer;
446  char ch;
447  unsigned int len, max;
448  unsigned int x;
449 
450  len = 0;
451  max = BLOCK_SIZE;
452  buffer = Allocate(max + 1);
453 
454  for(x = 0; !(IsEnd)(line[x]); x++) {
455  if(line[x] == '&') {
456  x += ParseEntity(line + x, &ch, file, *lineNumber) - 1;
457  if(ch) {
458  buffer[len] = ch;
459  } else {
460  buffer[len] = line[x];
461  }
462  } else {
463  if(line[x] == '\n') {
464  *lineNumber += 1;
465  }
466  buffer[len] = line[x];
467  }
468  len += 1;
469  if(len >= max) {
470  max += BLOCK_SIZE;
471  buffer = Reallocate(buffer, max + 1);
472  if(JUNLIKELY(buffer == NULL)) {
473  FatalError(_("out of memory"));
474  }
475  }
476  }
477  buffer[len] = 0;
478  Trim(buffer);
479  *offset = x;
480 
481  return buffer;
482 }
483 
485 char *ReadElementValue(const char *line,
486  const char *file,
487  unsigned int *offset,
488  unsigned int *lineNumber)
489 {
490  return ReadValue(line, file, IsValueEnd, offset, lineNumber);
491 }
492 
494 char *ReadAttributeValue(const char *line,
495  const char *file,
496  unsigned int *offset,
497  unsigned int *lineNumber)
498 {
499  return ReadValue(line, file, IsAttributeEnd, offset, lineNumber);
500 }
501 
503 TokenType LookupType(const char *name, TokenNode *np)
504 {
505  const int x = FindValue(TOKEN_MAP, TOKEN_MAP_COUNT, name);
506  if(x >= 0) {
507  if(np) {
508  np->type = x;
509  }
510  return x;
511  }
512 
513  if(JUNLIKELY(np)) {
514  np->type = TOK_INVALID;
515  np->invalidName = CopyString(name);
516  }
517 
518  return TOK_INVALID;
519 
520 }
521 
523 const char *GetTokenName(const TokenNode *tp)
524 {
525  if(tp->invalidName) {
526  return tp->invalidName;
527  } else {
528  return GetTokenTypeName(tp->type);
529  }
530 }
531 
533 const char *GetTokenTypeName(TokenType type) {
534  const char *key = FindKey(TOKEN_MAP, TOKEN_MAP_COUNT, type);
535  return key ? key : "[invalid]";
536 }
537 
539 TokenNode *CreateNode(TokenNode *current, const char *file,
540  unsigned int line)
541 {
542  TokenNode *np;
543 
544  np = Allocate(sizeof(TokenNode));
545  np->type = TOK_INVALID;
546  np->value = NULL;
547  np->attributes = NULL;
548  np->subnodeHead = NULL;
549  np->subnodeTail = NULL;
550  np->parent = current;
551  np->next = NULL;
552 
553  np->fileName = file;
554  np->line = line;
555  np->invalidName = NULL;
556 
557  if(current) {
558 
559  /* A node contained inside another node. */
560  if(current->subnodeHead) {
561  current->subnodeTail->next = np;
562  } else {
563  current->subnodeHead = np;
564  }
565  current->subnodeTail = np;
566 
567  } else if(!head) {
568 
569  /* The top-level node. */
570  head = np;
571 
572  } else {
573 
574  /* A duplicate top-level node.
575  * This is probably a configuration error.
576  */
577  ReleaseTokens(np);
578  np = head->subnodeTail ? head->subnodeTail : head;
579 
580  }
581 
582  return np;
583 }
584 
587 {
588  AttributeNode *ap;
589  ap = Allocate(sizeof(AttributeNode));
590  ap->name = NULL;
591  ap->value = NULL;
592  ap->next = np->attributes;
593  np->attributes = ap;
594  return ap;
595 }
596 
599 {
600 
601  AttributeNode *ap;
602  TokenNode *tp;
603 
604  while(np) {
605  tp = np->next;
606 
607  while(np->attributes) {
608  ap = np->attributes->next;
609  if(np->attributes->name) {
610  Release(np->attributes->name);
611  }
612  if(np->attributes->value) {
613  Release(np->attributes->value);
614  }
615  Release(np->attributes);
616  np->attributes = ap;
617  }
618 
619  if(np->subnodeHead) {
621  }
622 
623  if(np->value) {
624  Release(np->value);
625  }
626 
627  if(np->invalidName) {
628  Release(np->invalidName);
629  }
630 
631  Release(np);
632  np = tp;
633  }
634 
635 }

joewing.net / Projects / JWM