0 /* $OpenBSD$ */

1

2 /*

3 * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>

4 * Copyright (c) 2001 Markus Friedl. All rights reserved.

5 * Copyright (c) 2001 Daniel Hartmeier. All rights reserved.

6 * Copyright (c) 2001 Theo de Raadt. All rights reserved.

7 *

8 * Permission to use, copy, modify, and distribute this software for any

9 * purpose with or without fee is hereby granted, provided that the above

10 * copyright notice and this permission notice appear in all copies.

11 *

12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES

13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF

14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR

15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES

16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN

17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF

18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

19 */

20

21 %{

22

23 #include <sys/types.h>

24 #include <sys/queue.h>

25

26 #include <ctype.h>

27 #include <err.h>

28 #include <errno.h>

29 #include <limits.h>

30 #include <stdarg.h>

31 #include <stdio.h>

32 #include <stdlib.h>

33 #include <string.h>

34

35 #include "calmwm.h"

36

37 TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);

38 static struct file {

39 TAILQ_ENTRY(file) entry;

40 FILE *stream;

41 char *name;

42 int lineno;

43 int errors;

44 } *file, *topfile;

45 struct file *pushfile(const char *, FILE *);

46 int popfile(void);

47 int yyparse(void);

48 int yylex(void);

49 int yyerror(const char *, ...)

50 __attribute__((__format__ (printf, 1, 2)))

51 __attribute__((__nonnull__ (1)));

52 int kw_cmp(const void *, const void *);

53 int lookup(char *);

54 int lgetc(int);

55 int lungetc(int);

56 int findeol(void);

57

58 static struct conf *conf;

59

60 typedef struct {

61 union {

62 int64_t number;

63 char *string;

64 } v;

65 int lineno;

66 } YYSTYPE;

67

68 %}

69

70 %token BINDKEY UNBINDKEY BINDMOUSE UNBINDMOUSE

71 %token FONTNAME STICKY GAP

72 %token AUTOGROUP COMMAND IGNORE WM

73 %token YES NO BORDERWIDTH MOVEAMOUNT HTILE VTILE

74 %token COLOR SNAPDIST

75 %token ACTIVEBORDER INACTIVEBORDER URGENCYBORDER

76 %token GROUPBORDER UNGROUPBORDER

77 %token MENUBG MENUFG

78 %token FONTCOLOR FONTSELCOLOR

79 %token ERROR

80 %token <v.string> STRING

81 %token <v.number> NUMBER

82 %type <v.number> yesno

83 %type <v.string> string numberstring

84 %%

85

86 grammar : /* empty */

87 | grammar '\n'

88 | grammar main '\n'

89 | grammar color '\n'

90 | grammar error '\n' { file->errors++; }

91 ;

92

93 string : string STRING {

94 if (asprintf(&$, "%s %s", $1, $2) == -1) {

95 free($1);

96 free($2);

97 yyerror("string: asprintf");

98 YYERROR;

99 }

100 free($1);

101 free($2);

102 }

103 | STRING

104 ;

105

106 numberstring : NUMBER {

107 char *s;

108 if (asprintf(&s, "%lld", $1) == -1) {

109 yyerror("string: asprintf");

110 YYERROR;

111 }

112 $ = s;

113 }

114 | STRING

115 ;

116

117 yesno : YES { $ = 1; }

118 | NO { $ = 0; }

119 ;

120

121 main : FONTNAME STRING {

122 free(conf->font);

123 conf->font = $2;

124 }

125 | STICKY yesno {

126 conf->stickygroups = $2;

127 }

128 | BORDERWIDTH NUMBER {

129 if ($2 < 0 || $2 > INT_MAX) {

130 yyerror("invalid borderwidth");

131 YYERROR;

132 }

133 conf->bwidth = $2;

134 }

135 | HTILE NUMBER {

136 if ($2 < 0 || $2 > 99) {

137 yyerror("invalid htile percent");

138 YYERROR;

139 }

140 conf->htile = $2;

141 }

142 | VTILE NUMBER {

143 if ($2 < 0 || $2 > 99) {

144 yyerror("invalid vtile percent");

145 YYERROR;

146 }

147 conf->vtile = $2;

148 }

149 | MOVEAMOUNT NUMBER {

150 if ($2 < 0 || $2 > INT_MAX) {

151 yyerror("invalid movemount");

152 YYERROR;

153 }

154 conf->mamount = $2;

155 }

156 | SNAPDIST NUMBER {

157 if ($2 < 0 || $2 > INT_MAX) {

158 yyerror("invalid snapdist");

159 YYERROR;

160 }

161 conf->snapdist = $2;

162 }

163 | COMMAND STRING string {

164 if (strlen($3) >= PATH_MAX) {

165 yyerror("%s command path too long", $2);

166 free($2);

167 free($3);

168 YYERROR;

169 }

170 conf_cmd_add(conf, $2, $3);

171 free($2);

172 free($3);

173 }

174 | WM STRING string {

175 if (strlen($3) >= PATH_MAX) {

176 yyerror("%s wm path too long", $2);

177 free($2);

178 free($3);

179 YYERROR;

180 }

181 conf_wm_add(conf, $2, $3);

182 free($2);

183 free($3);

184 }

185 | AUTOGROUP NUMBER STRING {

186 if ($2 < 0 || $2 > 9) {

187 yyerror("invalid autogroup");

188 free($3);

189 YYERROR;

190 }

191 conf_autogroup(conf, $2, NULL, $3);

192 free($3);

193 }

194 | AUTOGROUP NUMBER STRING ',' STRING {

195 if ($2 < 0 || $2 > 9) {

196 yyerror("invalid autogroup");

197 free($3);

198 free($5);

199 YYERROR;

200 }

201 conf_autogroup(conf, $2, $3, $5);

202 free($3);

203 free($5);

204 }

205 | IGNORE STRING {

206 conf_ignore(conf, $2);

207 free($2);

208 }

209 | GAP NUMBER NUMBER NUMBER NUMBER {

210 if ($2 < 0 || $2 > INT_MAX ||

211 $3 < 0 || $3 > INT_MAX ||

212 $4 < 0 || $4 > INT_MAX ||

213 $5 < 0 || $5 > INT_MAX) {

214 yyerror("invalid gap");

215 YYERROR;

216 }

217 conf->gap.top = $2;

218 conf->gap.bottom = $3;

219 conf->gap.left = $4;

220 conf->gap.right = $5;

221 }

222 | BINDKEY numberstring string {

223 if (!conf_bind_key(conf, $2, $3)) {

224 yyerror("invalid bind-key: %s %s", $2, $3);

225 free($2);

226 free($3);

227 YYERROR;

228 }

229 free($2);

230 free($3);

231 }

232 | UNBINDKEY numberstring {

233 if (!conf_bind_key(conf, $2, NULL)) {

234 yyerror("invalid unbind-key: %s", $2);

235 free($2);

236 YYERROR;

237 }

238 free($2);

239 }

240 | BINDMOUSE numberstring string {

241 if (!conf_bind_mouse(conf, $2, $3)) {

242 yyerror("invalid bind-mouse: %s %s", $2, $3);

243 free($2);

244 free($3);

245 YYERROR;

246 }

247 free($2);

248 free($3);

249 }

250 | UNBINDMOUSE numberstring {

251 if (!conf_bind_mouse(conf, $2, NULL)) {

252 yyerror("invalid unbind-mouse: %s", $2);

253 free($2);

254 YYERROR;

255 }

256 free($2);

257 }

258 ;

259

260 color : COLOR colors

261 ;

262

263 colors : ACTIVEBORDER STRING {

264 free(conf->color[CWM_COLOR_BORDER_ACTIVE]);

265 conf->color[CWM_COLOR_BORDER_ACTIVE] = $2;

266 }

267 | INACTIVEBORDER STRING {

268 free(conf->color[CWM_COLOR_BORDER_INACTIVE]);

269 conf->color[CWM_COLOR_BORDER_INACTIVE] = $2;

270 }

271 | URGENCYBORDER STRING {

272 free(conf->color[CWM_COLOR_BORDER_URGENCY]);

273 conf->color[CWM_COLOR_BORDER_URGENCY] = $2;

274 }

275 | GROUPBORDER STRING {

276 free(conf->color[CWM_COLOR_BORDER_GROUP]);

277 conf->color[CWM_COLOR_BORDER_GROUP] = $2;

278 }

279 | UNGROUPBORDER STRING {

280 free(conf->color[CWM_COLOR_BORDER_UNGROUP]);

281 conf->color[CWM_COLOR_BORDER_UNGROUP] = $2;

282 }

283 | MENUBG STRING {

284 free(conf->color[CWM_COLOR_MENU_BG]);

285 conf->color[CWM_COLOR_MENU_BG] = $2;

286 }

287 | MENUFG STRING {

288 free(conf->color[CWM_COLOR_MENU_FG]);

289 conf->color[CWM_COLOR_MENU_FG] = $2;

290 }

291 | FONTCOLOR STRING {

292 free(conf->color[CWM_COLOR_MENU_FONT]);

293 conf->color[CWM_COLOR_MENU_FONT] = $2;

294 }

295 | FONTSELCOLOR STRING {

296 free(conf->color[CWM_COLOR_MENU_FONT_SEL]);

297 conf->color[CWM_COLOR_MENU_FONT_SEL] = $2;

298 }

299 ;

300 %%

301

302 struct keywords {

303 const char *k_name;

304 int k_val;

305 };

306

307 int

308 yyerror(const char *fmt, ...)

309 {

310 va_list ap;

311

312 file->errors++;

313 va_start(ap, fmt);

314 fprintf(stderr, "%s:%d: ", file->name, yylval.lineno);

315 vfprintf(stderr, fmt, ap);

316 fprintf(stderr, "\n");

317 va_end(ap);

318 return (0);

319 }

320

321 int

322 kw_cmp(const void *k, const void *e)

323 {

324 return (strcmp(k, ((const struct keywords *)e)->k_name));

325 }

326

327 int

328 lookup(char *s)

329 {

330 /* this has to be sorted always */

331 static const struct keywords keywords[] = {

332 { "activeborder", ACTIVEBORDER},

333 { "autogroup", AUTOGROUP},

334 { "bind-key", BINDKEY},

335 { "bind-mouse", BINDMOUSE},

336 { "borderwidth", BORDERWIDTH},

337 { "color", COLOR},

338 { "command", COMMAND},

339 { "font", FONTCOLOR},

340 { "fontname", FONTNAME},

341 { "gap", GAP},

342 { "groupborder", GROUPBORDER},

343 { "htile", HTILE},

344 { "ignore", IGNORE},

345 { "inactiveborder", INACTIVEBORDER},

346 { "menubg", MENUBG},

347 { "menufg", MENUFG},

348 { "moveamount", MOVEAMOUNT},

349 { "no", NO},

350 { "selfont", FONTSELCOLOR},

351 { "snapdist", SNAPDIST},

352 { "sticky", STICKY},

353 { "unbind-key", UNBINDKEY},

354 { "unbind-mouse", UNBINDMOUSE},

355 { "ungroupborder", UNGROUPBORDER},

356 { "urgencyborder", URGENCYBORDER},

357 { "vtile", VTILE},

358 { "wm", WM},

359 { "yes", YES}

360 };

361 const struct keywords *p;

362

363 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),

364 sizeof(keywords[0]), kw_cmp);

365

366 if (p)

367 return (p->k_val);

368 else

369 return (STRING);

370 }

371

372 #define MAXPUSHBACK 128

373

374 char *parsebuf;

375 int parseindex;

376 char pushback_buffer[MAXPUSHBACK];

377 int pushback_index = 0;

378

379 int

380 lgetc(int quotec)

381 {

382 int c, next;

383

384 if (parsebuf) {

385 /* Read character from the parsebuffer instead of input. */

386 if (parseindex >= 0) {

387 c = (unsigned char)parsebuf[parseindex++];

388 if (c != '\0')

389 return (c);

390 parsebuf = NULL;

391 } else

392 parseindex++;

393 }

394

395 if (pushback_index)

396 return ((unsigned char)pushback_buffer[--pushback_index]);

397

398 if (quotec) {

399 if ((c = getc(file->stream)) == EOF) {

400 yyerror("reached end of file while parsing "

401 "quoted string");

402 if (file == topfile || popfile() == EOF)

403 return (EOF);

404 return (quotec);

405 }

406 return (c);

407 }

408

409 while ((c = getc(file->stream)) == '\\') {

410 next = getc(file->stream);

411 if (next != '\n') {

412 c = next;

413 break;

414 }

415 yylval.lineno = file->lineno;

416 file->lineno++;

417 }

418

419 while (c == EOF) {

420 if (file == topfile || popfile() == EOF)

421 return (EOF);

422 c = getc(file->stream);

423 }

424 return (c);

425 }

426

427 int

428 lungetc(int c)

429 {

430 if (c == EOF)

431 return (EOF);

432 if (parsebuf) {

433 parseindex--;

434 if (parseindex >= 0)

435 return (c);

436 }

437 if (pushback_index + 1 >= MAXPUSHBACK)

438 return (EOF);

439 pushback_buffer[pushback_index++] = c;

440 return (c);

441 }

442

443 int

444 findeol(void)

445 {

446 int c;

447

448 parsebuf = NULL;

449

450 /* skip to either EOF or the first real EOL */

451 while (1) {

452 if (pushback_index)

453 c = (unsigned char)pushback_buffer[--pushback_index];

454 else

455 c = lgetc(0);

456 if (c == '\n') {

457 file->lineno++;

458 break;

459 }

460 if (c == EOF)

461 break;

462 }

463 return (ERROR);

464 }

465

466 int

467 yylex(void)

468 {

469 char buf[8096];

470 char *p;

471 int quotec, next, c;

472 int token;

473

474 p = buf;

475 while ((c = lgetc(0)) == ' ' || c == '\t')

476 ; /* nothing */

477

478 yylval.lineno = file->lineno;

479 if (c == '#')

480 while ((c = lgetc(0)) != '\n' && c != EOF)

481 ; /* nothing */

482

483 switch (c) {

484 case '\'':

485 case '"':

486 quotec = c;

487 while (1) {

488 if ((c = lgetc(quotec)) == EOF)

489 return (0);

490 if (c == '\n') {

491 file->lineno++;

492 continue;

493 } else if (c == '\\') {

494 if ((next = lgetc(quotec)) == EOF)

495 return (0);

496 if (next == quotec || next == ' ' ||

497 next == '\t')

498 c = next;

499 else if (next == '\n') {

500 file->lineno++;

501 continue;

502 } else

503 lungetc(next);

504 } else if (c == quotec) {

505 *p = '\0';

506 break;

507 } else if (c == '\0') {

508 yyerror("syntax error");

509 return (findeol());

510 }

511 if (p + 1 >= buf + sizeof(buf) - 1) {

512 yyerror("string too long");

513 return (findeol());

514 }

515 *p++ = c;

516 }

517 yylval.v.string = xstrdup(buf);

518 return (STRING);

519 }

520

521 #define allowed_to_end_number(x) \

522 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')

523

524 if (c == '-' || isdigit(c)) {

525 do {

526 *p++ = c;

527 if ((size_t)(p-buf) >= sizeof(buf)) {

528 yyerror("string too long");

529 return (findeol());

530 }

531 } while ((c = lgetc(0)) != EOF && isdigit(c));

532 lungetc(c);

533 if (p == buf + 1 && buf[0] == '-')

534 goto nodigits;

535 if (c == EOF || allowed_to_end_number(c)) {

536 const char *errstr = NULL;

537

538 *p = '\0';

539 yylval.v.number = strtonum(buf, LLONG_MIN,

540 LLONG_MAX, &errstr);

541 if (errstr) {

542 yyerror("\"%s\" invalid number: %s",

543 buf, errstr);

544 return (findeol());

545 }

546 return (NUMBER);

547 } else {

548 nodigits:

549 while (p > buf + 1)

550 lungetc((unsigned char)*--p);

551 c = (unsigned char)*--p;

552 if (c == '-')

553 return (c);

554 }

555 }

556

557 /* Similar to other parse.y copies, but also allows '/' in strings */

558 #define allowed_in_string(x) \

559 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \

560 x != '{' && x != '}' && x != '<' && x != '>' && \

561 x != '!' && x != '=' && x != '#' && x != ','))

562

563 if (isalnum(c) || c == ':' || c == '_' || c == '*' || c == '/') {

564 do {

565 *p++ = c;

566 if ((size_t)(p-buf) >= sizeof(buf)) {

567 yyerror("string too long");

568 return (findeol());

569 }

570 } while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));

571 lungetc(c);

572 *p = '\0';

573 if ((token = lookup(buf)) == STRING)

574 yylval.v.string = xstrdup(buf);

575 return (token);

576 }

577 if (c == '\n') {

578 yylval.lineno = file->lineno;

579 file->lineno++;

580 }

581 if (c == EOF)

582 return (0);

583 return (c);

584 }

585

586 struct file *

587 pushfile(const char *name, FILE *stream)

588 {

589 struct file *nfile;

590

591 nfile = xcalloc(1, sizeof(struct file));

592 nfile->name = xstrdup(name);

593 nfile->stream = stream;

594 nfile->lineno = 1;

595 TAILQ_INSERT_TAIL(&files, nfile, entry);

596 return (nfile);

597 }

598

599 int

600 popfile(void)

601 {

602 struct file *prev;

603

604 if ((prev = TAILQ_PREV(file, files, entry)) != NULL)

605 prev->errors += file->errors;

606

607 TAILQ_REMOVE(&files, file, entry);

608 fclose(file->stream);

609 free(file->name);

610 free(file);

611 file = prev;

612 return (file ? 0 : EOF);

613 }

614

615 int

616 parse_config(const char *filename, struct conf *xconf)

617 {

618 FILE *stream;

619 int errors = 0;

620

621 conf = xconf;

622

623 stream = fopen(filename, "r");

624 if (stream == NULL) {

625 if (errno == ENOENT)

626 return (0);

627 warn("%s", filename);

628 return (-1);

629 }

630 file = pushfile(filename, stream);

631 topfile = file;

632

633 yyparse();

634 errors = file->errors;

635 popfile();

636

637 return (errors ? -1 : 0);

638 }

639