hirc

[archived] IRC client
git clone https://hhvn.uk/hirc
git clone git://hhvn.uk/hirc
Log | Files | Refs

format.y (15486B)


      1 %{
      2 #include <stdio.h>
      3 #include <stdlib.h>
      4 #include <stdarg.h>
      5 #include <string.h>
      6 #include <ctype.h>
      7 #include "hirc.h"
      8 #include "data/formats.h"
      9 
     10 #define YYSTYPE char *
     11 
     12 static void parse_append(char **dest, char *str);
     13 static char *parse_dup(char *str);
     14 static char *parse_printf(char *fmt, ...);
     15 static void yyerror(char *msg);
     16 static int yylex(void);
     17 
     18 #define BRACEMAX 16
     19 #define RETURN(s) do {prev = s; return s;} while (0)
     20 #define ISSPECIAL(s) isspecial(s, prev, bracelvl, bracetype, styleseg)
     21 
     22 enum {
     23 	PARSE_TIME,
     24 	PARSE_LEFT,
     25 	PARSE_RIGHT,
     26 	PARSE_LAST
     27 };
     28 
     29 static int parse_error = 0;
     30 static char *parse_in = NULL;
     31 static char *parse_out[PARSE_LAST] = {NULL, NULL, NULL};
     32 static int parse_pos = PARSE_LEFT;
     33 static char **parse_params = NULL;
     34 static struct Server *parse_server = NULL;
     35 static int parse_divbool = 0;
     36 
     37 enum {
     38 	var_raw,
     39 	var_cmd,
     40 	var_nick,
     41 	var_ident,
     42 	var_host,
     43 	var_priv,
     44 	var_channel,
     45 	var_topic,
     46 	var_server,
     47 	var_time,
     48 };
     49 static struct {
     50 	char *name;
     51 	char *val;
     52 } vars[] = {
     53 	[var_raw]	= {"raw", NULL},
     54 	[var_cmd]	= {"cmd", NULL},
     55 	[var_nick]	= {"nick", NULL},
     56 	[var_ident]	= {"ident", NULL},
     57 	[var_host]	= {"host", NULL},
     58 	[var_priv]	= {"priv", NULL},
     59 	[var_channel]	= {"channel", NULL},
     60 	[var_topic]	= {"topic", NULL},
     61 	[var_server]	= {"server", NULL},
     62 	[var_time]	= {"time", NULL},
     63 	{NULL, NULL},
     64 };
     65 %}
     66 
     67 %token VAR STYLE LBRACE RBRACE COLON COMMA
     68 %token STRING
     69 
     70 %%
     71 
     72 grammar: /* empty */ { parse_append(&parse_out[parse_pos], ""); }
     73        	| grammar var { parse_append(&parse_out[parse_pos], $2); }
     74 	| grammar style { parse_append(&parse_out[parse_pos], $2); }
     75 	| grammar STRING { parse_append(&parse_out[parse_pos], $2); }
     76        	;
     77 
     78 var:	VAR LBRACE STRING RBRACE {
     79    		char buf[8192];
     80 		char *tmp;
     81      		int i, num;
     82 		if (strisnum($3, 0) && (num = strtoll($3, NULL, 10)) && param_len(parse_params) >= num) {
     83 			if (num == 2 && **(parse_params + num - 1) == 1 &&
     84 					(strcmp_n(vars[var_cmd].val, "PRIVMSG") == 0 || strcmp_n(vars[var_cmd].val, "NOTICE") == 0)) {
     85 				if (strncmp(*(parse_params + num - 1) + 1, "ACTION", CONSTLEN("ACTION")) == 0)
     86 					tmp = parse_dup(*(parse_params + num - 1) + 1 + CONSTLEN("ACTION "));
     87 				else
     88 					tmp = parse_dup(*(parse_params + num - 1) + 1);
     89 				if (tmp[strlen(tmp) - 1] == 1)
     90 					tmp[strlen(tmp) - 1] = '\0';
     91 				$$ = tmp;
     92 			} else $$ = *(parse_params + num - 1);
     93 			goto varfin;
     94 		}
     95 		if (*$3 && *($3 + strlen($3) - 1) == '-') {
     96 			*($3 + strlen($3) - 1) = '\0';
     97 			if (strisnum($3, 0) && (num = strtoll($3, NULL, 10)) && param_len(parse_params) >= num) {
     98 				buf[0] = '\0';
     99 				for (i = num; i <= param_len(parse_params); i++) {
    100 					strlcat(buf, *(parse_params + i - 1), sizeof(buf));
    101 					strlcat(buf, " ", sizeof(buf));
    102 				}
    103 				$$ = parse_dup(buf);
    104 				goto varfin;
    105 			} else {
    106 				*($3 + strlen($3) - 1) = '\0';
    107 			}
    108 		}
    109      		for (i = 0; vars[i].name; i++) {
    110 			if (vars[i].val && strcmp(vars[i].name, $3) == 0) {
    111 				$$ = parse_printf("%s", vars[i].val);
    112 				goto varfin;
    113 			}
    114 		}
    115 		$$ = parse_printf("${%s}", $3);
    116 varfin:
    117       		((void)0);
    118 	}
    119 	;
    120 
    121 style:	STYLE LBRACE STRING RBRACE {
    122      		if (strcmp($3, "b") == 0) {
    123 			$$ = parse_printf("\02"); /* ^B */
    124      		} else if (strcmp($3, "c") == 0) {
    125 			$$ = parse_printf("\0399,99"); /* ^C */
    126      		} else if (strcmp($3, "i") == 0) {
    127 			$$ = parse_printf("\011"); /* ^I */
    128      		} else if (strcmp($3, "o") == 0) {
    129 			$$ = parse_printf("\017"); /* ^O */
    130      		} else if (strcmp($3, "r") == 0) {
    131 			$$ = parse_printf("\022"); /* ^R */
    132      		} else if (strcmp($3, "u") == 0) {
    133 			$$ = parse_printf("\025"); /* ^U */
    134      		} else if (strcmp($3, "=") == 0) {
    135 			if (parse_pos == PARSE_LEFT) {
    136 				parse_pos = PARSE_RIGHT;
    137 				$$ = NULL;
    138 			} else {
    139 				$$ = parse_dup(" ");
    140 			}
    141 		} else if (strcmp($3, "_time") == 0) { /* special style to mark end of timestamp */
    142 			if (parse_pos == PARSE_TIME) {
    143 				parse_pos = parse_divbool ? PARSE_LEFT : PARSE_RIGHT;
    144 				$$ = NULL;
    145 			} else {
    146 				yyerror("%{_time} used erroneously here: "
    147 					"this style is meant for internal use only, "
    148 					"this isn't a bug if you manually inserted this in a format");
    149 				YYERROR;
    150 			}
    151 		} else {
    152 			$$ = parse_printf("%%{%s}", $3);
    153 		}
    154 	}
    155 	| STYLE LBRACE STRING COLON sstring RBRACE {
    156 		struct Nick *nick;
    157 		if (strcmp($3, "c") == 0) {
    158 			if (strlen($5) <= 2 && isdigit(*$5) && (!*($5+1) || isdigit(*($5+1))))
    159 				$$ = parse_printf("\03%02d" /* ^C */, atoi($5));
    160 		} else if (strcmp($3, "nick") == 0) {
    161 			nick = nick_create($5, ' ', parse_server);
    162 			$$ = parse_printf("\03%02d" /* ^C */, nick_getcolour(nick));
    163 			nick_free(nick);
    164 		} else if (strcmp($3, "rdate") == 0) {
    165 			if (strisnum($5, 0)) {
    166 				$$ = parse_printf("%s", strrdate((time_t)strtoll($5, NULL, 10)));
    167 			} else {
    168 				yyerror("invalid date in invocation of %{rdate:...}");
    169 				YYERROR;
    170 			}
    171 		} else {
    172 			$$ = parse_printf("%%{%s:%s}", $3, $5);
    173 		}
    174 	}
    175 	| STYLE LBRACE STRING COLON sstring COMMA sstring RBRACE {
    176 		char stime[1024];
    177 		time_t dtime;
    178 		if (strcmp($3, "c") == 0) {
    179 			if (strlen($5) <= 2 && isdigit(*$5) && (!*($5+1) || isdigit(*($5+1))) &&
    180 					strlen($7) <= 2 && isdigit(*$7) && (!*($7+1) || isdigit(*($5+1))))
    181 				$$ = parse_printf("\03%02d,%02d" /* ^C */, atoi($5), atoi($7));
    182 			else
    183 				$$ = parse_printf("%%{%s:%s,%s}", $3, $5, $7);
    184 		} else if (strcmp($3, "pad") == 0) {
    185 			if (strisnum($5, 1)) {
    186 				$$ = parse_printf("%1$*2$s", $7, (int)strtoll($5, NULL, 0));
    187 			} else {
    188 				yyerror("second argument to %{pad:...} must be a integer");
    189 				YYERROR;
    190 			}
    191 		} else if (strcmp($3, "time") == 0) {
    192 			if (strisnum($7, 0)) {
    193 				dtime = (time_t)strtoll($7, NULL, 0);
    194 				strftime(stime, sizeof(stime), $5, localtime(&dtime));
    195 				$$ = parse_dup(stime);
    196 			} else {
    197 				yyerror("invalid date in invocation of %{time:...}");
    198 				YYERROR;
    199 			}
    200 		} else {
    201 			$$ = parse_printf("%%{%s:%s,%s}", $3, $5, $7);
    202 		}
    203 	}
    204 	| STYLE LBRACE STRING COLON sstring COMMA sstring COMMA sstring RBRACE {
    205 		int num;
    206 		char *val;
    207 		if (strcmp($3, "split") == 0) {
    208 			num = strtoll($5, NULL, 10);
    209 			val = strntok($9, $7, num);
    210 			if (strisnum($5, 0) && val) {
    211 				$$ = parse_dup(val);
    212 			} else if (strisnum($5, 0)) {
    213 				$$ = NULL;
    214 			} else {
    215 				yyerror("first argument to %{split:...} must be an integer");
    216 				YYERROR;
    217 			}
    218 		} else {
    219 			$$ = parse_printf("%%{%s:%s,%s,%s}", $3, $5, $7, $9);
    220 		}
    221 	}
    222 	;
    223 
    224 sstring: STRING
    225        | var
    226        | style
    227        ;
    228 
    229 %%
    230 
    231 static void
    232 yyerror(char *msg) {
    233 	parse_error = 1;
    234 	ui_error("parsing '%s': %s", parse_in, msg);
    235 }
    236 
    237 /* Keep in mind: parse_append doesn't use parse_dup. */
    238 static void
    239 parse_append(char **dest, char *str) {
    240 	size_t size;
    241 	size_t len;
    242 
    243 	if (!str)
    244 		return;
    245 
    246 	if (*dest)
    247 		len = strlen(*dest);
    248 	else
    249 		len = 0;
    250 
    251 	size = len + strlen(str) + 1;
    252 	if (!*dest)
    253 		*dest = emalloc(size);
    254 	else
    255 		*dest = erealloc(*dest, size);
    256 	
    257 	(len ? strlcat : strlcpy)(*dest, str, size);
    258 }
    259 
    260 /* alloc memory to be free'd all at once when parsing is complete
    261  * pfree() must not be called on anything returned */
    262 static char *
    263 parse_dup(char *str) {
    264 	static void **mema = NULL;
    265 	static size_t mems = 0;
    266 	void *mem = NULL;
    267 	size_t i;
    268 
    269 	if (str) {
    270 		mem = strdup(str);
    271 		if (!mems)
    272 			mema = emalloc((sizeof(char *)) * (mems + 1));
    273 		else
    274 			mema = erealloc(mema, (sizeof(char *)) * (mems + 1));
    275 
    276 		*(mema + mems) = mem;
    277 		mems++;
    278 	} else if (mema && mems) {
    279 		for (i = 0; i < mems; i++)
    280 			pfree(mema + i); /* already a double pointer */
    281 		pfree(&mema);
    282 		mems = 0;
    283 		mema = NULL;
    284 	}
    285 
    286 	return mem;
    287 }
    288 
    289 static char *
    290 parse_printf(char *format, ...) {
    291 	va_list ap;
    292 	char buf[8192];
    293 
    294 	va_start(ap, format);
    295 	vsnprintf(buf, sizeof(buf), format, ap);
    296 	va_end(ap);
    297 	return parse_dup(buf);
    298 }
    299 
    300 static int
    301 isspecial(char *s, int prev, int bracelvl, int bracetype[static BRACEMAX], int styleseg[static BRACEMAX]) {
    302 	if ((*s == '$' && (*(s+1) == '{' && bracelvl != BRACEMAX)) ||
    303 			(*s == '%' && (*(s+1) == '{' && bracelvl != BRACEMAX)) ||
    304 			(*s == '{' && (prev == VAR || prev == STYLE)) ||
    305 			(*s == '}' && (bracelvl)) ||
    306 			(*s == ',' && (bracelvl && bracetype[bracelvl - 1] == STYLE && styleseg[bracelvl - 1])) ||
    307 			(*s == ':' && (bracelvl && bracetype[bracelvl - 1] == STYLE && !styleseg[bracelvl - 1])))
    308 		return 1;
    309 	else
    310 		return 0;
    311 }
    312 
    313 static int
    314 yylex(void) {
    315 	static char *s = NULL, *p = NULL;
    316 	static int bracelvl;
    317 	static int bracetype[BRACEMAX];
    318 	static int styleseg[BRACEMAX];
    319 	static int prev = 0;
    320 	char strlval[8192];
    321 	int i;
    322 
    323 	if (!s || prev == 0) {
    324 		s = parse_in;
    325 		bracelvl = 0;
    326 		prev = -1;
    327 	}
    328 
    329 	if (!*s)
    330 		RETURN(0);
    331 
    332 	if (ISSPECIAL(s)) {
    333 		switch (*s) {
    334 		case '$':
    335 			s++;
    336 			RETURN(VAR);
    337 		case '%':
    338 			s++;
    339 			RETURN(STYLE);
    340 		case '{':
    341 			bracelvl++;
    342 			bracetype[bracelvl - 1] = prev;
    343 			if (prev == STYLE)
    344 				styleseg[bracelvl - 1] = 0;
    345 			s++;
    346 			RETURN(LBRACE);
    347 		case '}':
    348 			bracelvl--;
    349 			s++;
    350 			RETURN(RBRACE);
    351 		case ',':
    352 			styleseg[bracelvl - 1]++;
    353 			s++;
    354 			RETURN(COMMA);
    355 		case ':':
    356 			styleseg[bracelvl - 1]++;
    357 			s++;
    358 			RETURN(COLON);
    359 		}
    360 	}
    361 
    362 	/* first char guaranteed due to previous ISSPECIAL() */
    363 	strlval[0] = *s;
    364 
    365 	for (p = s + 1, i = 1; *p && i < sizeof(strlval); p++) {
    366 		if (ISSPECIAL(p))
    367 			break;
    368 		if (*p == '\\' && ISSPECIAL(p+1)) {
    369 			strlval[i++] = *(p+1);
    370 			p++;
    371 		} else if (*p == '\\' && *(p+1) == 'n') {
    372 			strlval[i++] = '\n';
    373 			p++;
    374 		} else {
    375 			strlval[i++] = *p;
    376 		}
    377 	}
    378 
    379 	strlval[i] = '\0';
    380 	yylval = parse_dup(strlval);
    381 	s = p;
    382 	RETURN(STRING);
    383 }
    384 
    385 /*
    386  * Exposed functions
    387  */
    388 
    389 char *
    390 format_get(struct History *hist) {
    391 	char *cmd, *p1, *p2;
    392 	int i;
    393 
    394 	assert_warn(hist, NULL);
    395 
    396 	if (!hist->params)
    397 		goto raw;
    398 
    399 	cmd = *(hist->params);
    400 	p1 = *(hist->params+1);
    401 	p2 = *(hist->params+2);
    402 
    403 	if (strcmp_n(cmd, "MODE") == 0) {
    404 		if (p1 && serv_ischannel(hist->origin->server, p1))
    405 			cmd = "MODE-CHANNEL";
    406 		else if (hist->from && nick_isself(hist->from) && strcmp_n(hist->from->nick, p1) == 0)
    407 			cmd = "MODE-NICK-SELF";
    408 		else
    409 			cmd = "MODE-NICK";
    410 	} else if (strcmp_n(cmd, "PRIVMSG") == 0) {
    411 		/* ascii 1 is ^A */
    412 		if (*p2 == 1 && strncmp(p2 + 1, "ACTION", CONSTLEN("ACTION")) == 0)
    413 			cmd = "PRIVMSG-ACTION";
    414 		else if (*p2 == 1)
    415 			cmd = "PRIVMSG-CTCP";
    416 	} else if (strcmp_n(cmd, "NOTICE") == 0 && *p2 == 1) {
    417 		cmd = "NOTICE-CTCP";
    418 	}
    419 
    420 	for (i=0; formatmap[i].cmd; i++)
    421 		if (formatmap[i].format && strcmp_n(formatmap[i].cmd, cmd) == 0)
    422 			return formatmap[i].format;
    423 
    424 	if (isdigit(*cmd) && isdigit(*(cmd+1)) && isdigit(*(cmd+2)) && !*(cmd+3))
    425 		return "format.rpl.other";
    426 
    427 raw:
    428 	return "format.other";
    429 }
    430 
    431 char *
    432 format(struct Window *window, char *format, struct History *hist) {
    433 	char buf[4096];
    434 	char *ts;
    435 	char *rformat;
    436 	int x;
    437 	size_t len, pad;
    438 	int clen[PARSE_LAST]; /* ui_strlenc */
    439 	int alen[PARSE_LAST]; /* strlen */
    440 	int divlen = config_getl("divider.margin");
    441 	int divbool = 0;
    442 	char *divstr = config_gets("divider.string");
    443 	char priv[2];
    444 	size_t i;
    445 
    446 	assert_warn(format || hist, NULL);
    447 	if (!format)
    448 		format = config_gets(format_get(hist));
    449 	assert_warn(format, NULL);
    450 
    451 	vars[var_channel].val = selected.channel ? selected.channel->name  : NULL;
    452 	vars[var_topic].val   = selected.channel ? selected.channel->topic : NULL;
    453 	vars[var_server].val  = selected.server  ? selected.server->name   : NULL;
    454 
    455 	if (hist) {
    456 		vars[var_raw].val   = hist->raw;
    457 		vars[var_nick].val  = hist->from ? hist->from->nick  : NULL;
    458 		vars[var_ident].val = hist->from ? hist->from->ident : NULL;
    459 		vars[var_host].val  = hist->from ? hist->from->host  : NULL;
    460 
    461 		if (hist->from) {
    462 			priv[0] = hist->from->priv;
    463 			priv[priv[0] != ' '] = '\0';
    464 			vars[var_priv].val = priv;
    465 		}
    466 
    467 		if (hist->origin) {
    468 			if (hist->origin->channel) {
    469 				divbool = config_getl("divider.toggle");
    470 				vars[var_channel].val = hist->origin->channel->name;
    471 				vars[var_topic].val   = hist->origin->channel->topic;
    472 			}
    473 			parse_server = hist->origin->server;
    474 			if (hist->origin->server) {
    475 				vars[var_server].val  = hist->origin->server->name;
    476 			}
    477 		}
    478 
    479 		len = snprintf(vars[var_time].val, 0, "%lld", (long long)hist->timestamp) + 1;
    480 		vars[var_time].val = emalloc(len);
    481 		snprintf(vars[var_time].val, len, "%lld", (long long)hist->timestamp);
    482 
    483 		vars[var_cmd].val = *hist->params;
    484 		if (hist->params)
    485 			parse_params = hist->params + 1;
    486 	} else {
    487 		vars[var_raw].val = vars[var_nick].val = vars[var_ident].val =
    488 			vars[var_host].val = vars[var_priv].val = NULL;
    489 		parse_params = NULL;
    490 		parse_server = NULL;
    491 	}
    492 
    493 	if (hist && config_getl("timestamp.toggle")) {
    494 		ts = config_gets("format.ui.timestamp");
    495 		len = strlen(ts) + strlen(format) + CONSTLEN("%{_time}") + 1;
    496 		rformat = emalloc(len);
    497 		snprintf(rformat, len, "%s%%{_time}%s", ts, format);
    498 		parse_pos = PARSE_TIME;
    499 	} else {
    500 		rformat = strdup(format);
    501 		parse_pos = divbool ? PARSE_LEFT : PARSE_RIGHT;
    502 	}
    503 
    504 	parse_dup(0); /* free memory in use for last parse_ */
    505 	for (i = 0; i < PARSE_LAST; i++)
    506 		pfree(&parse_out[i]);
    507 	parse_in = rformat;
    508 	parse_divbool = divbool;
    509 
    510 	yyparse();
    511 	pfree(&rformat);
    512 
    513 	if (parse_error) {
    514 		parse_error = 0;
    515 		return NULL;
    516 	}
    517 
    518 	if (parse_out[PARSE_LEFT] && !parse_out[PARSE_RIGHT]) {
    519 		parse_out[PARSE_RIGHT] = parse_out[PARSE_LEFT];
    520 		parse_out[PARSE_LEFT] = NULL;
    521 	}
    522 
    523 	for (i = 0; i < PARSE_LAST; i++) {
    524 		clen[i] = parse_out[i] ? ui_strlenc(&windows[Win_main], parse_out[i], NULL) : 0;
    525 		alen[i] = parse_out[i] ? strlen(parse_out[i]) : 0;
    526 	}
    527 
    528 	if (divbool) {
    529 		if (window)
    530 			pad = clen[PARSE_TIME] + divlen + ui_strlenc(NULL, divstr, NULL) + 1;
    531 		snprintf(buf, sizeof(buf), "%1$s %2$*3$s%4$s%5$s",
    532 				parse_out[PARSE_TIME] ? parse_out[PARSE_TIME] : "",
    533 				parse_out[PARSE_LEFT] ? parse_out[PARSE_LEFT] : "", divlen + alen[PARSE_LEFT] - clen[PARSE_LEFT],
    534 				divstr,
    535 				parse_out[PARSE_RIGHT]);
    536 	} else {
    537 		/* If divbool is zero, then all text is in either PARSE_TIME or PARSE_RIGHT */
    538 		if (window)
    539 			pad = clen[PARSE_TIME];
    540 		snprintf(buf, sizeof(buf), "%s%s",
    541 				parse_out[PARSE_TIME] ? parse_out[PARSE_TIME] : "",
    542 				parse_out[PARSE_RIGHT] ? parse_out[PARSE_RIGHT] : "");
    543 	}
    544 
    545 	/* Previously this function would attempt to calculate the size needed and allocate a string for it.
    546 	 * Now it uses a buffer and duplicates the string that is written to the buffer. This should be fine
    547 	 * since IRC messages are capped at 512 bytes long, leaving plenty of space in 4096 bytes to write
    548 	 * the formatted string to. */
    549 
    550 	if (window) {
    551 		for (i = x = 0; buf[i]; i++) {
    552 			if (i >= sizeof(buf) - 6) { /* 6 due to ^C handling */
    553 				ui_error("The buffer was too small to fit a string. Somehow.", NULL);
    554 				break;
    555 			}
    556 
    557 			/* taken from ui_strlenc */
    558 			switch (buf[i]) {
    559 			case 2: case 9: case 15: case 18: case 21:
    560 				break;
    561 			case 3:  /* ^C */
    562 				if (buf[i] && isdigit(buf[i+1]))
    563 					i += 1;
    564 				if (buf[i] && isdigit(buf[i+1]))
    565 					i += 1;
    566 				if (buf[i] && buf[i+1] == ',' && isdigit(buf[i+2]))
    567 					i += 2;
    568 				if (buf[i] && i && buf[i-1] == ',' && isdigit(buf[i+1]))
    569 					i += 1;
    570 				break;
    571 			default:
    572 				if ((buf[i] & 0xC0) != 0x80)
    573 					while ((buf[i + 1] & 0xC0) == 0x80 && i < sizeof(buf))
    574 						i++;
    575 				x++;
    576 			}
    577 
    578 			if (x == window->w) {
    579 				x = 0;
    580 				if (i + pad + strlen(buf + i) >= sizeof(buf) - 1)
    581 					break;
    582 				memmove(buf + i + pad, buf + i, strlen(buf + i) + 1);
    583 				if (parse_out[PARSE_TIME])
    584 					memset(buf + i + 1, ' ', clen[PARSE_TIME]);
    585 				if (divbool) {
    586 					memset(buf + i + 1 + clen[PARSE_TIME], ' ', pad - clen[PARSE_TIME]);
    587 					memcpy(buf + i + 1 + pad - strlen(divstr), divstr, strlen(divstr));
    588 				}
    589 				/* no need to increment i, as all the text
    590 				 * inserted here is after buf[i] and will be
    591 				 * counted in this for loop */
    592 			}
    593 		}
    594 	}
    595 
    596 	buf[i] = '\0';
    597 
    598 	return estrdup(buf);
    599 }