Code

801462617755d305739e4ef28ac237dd8ce4d5cd
[sysdb.git] / src / parser / grammar.y
1 /*
2  * SysDB - src/parser/grammar.y
3  * Copyright (C) 2013-2015 Sebastian 'tokkee' Harl <sh@tokkee.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
28 /*
29  * Grammar for the SysDB Query Language (SysQL).
30  */
32 %{
34 #include "core/store.h"
35 #include "core/time.h"
37 #include "parser/ast.h"
38 #include "parser/parser.h"
39 #include "parser/grammar.h"
41 #include "utils/error.h"
42 #include "utils/llist.h"
44 #include <assert.h>
46 #include <stdio.h>
47 #include <string.h>
49 /*
50  * public API
51  */
53 int
54 sdb_parser_yylex(YYSTYPE *yylval, YYLTYPE *yylloc, sdb_parser_yyscan_t yyscanner);
56 sdb_parser_yyextra_t *
57 sdb_parser_yyget_extra(sdb_parser_yyscan_t scanner);
59 void
60 sdb_parser_yyerror(YYLTYPE *lval, sdb_parser_yyscan_t scanner, const char *msg);
61 void
62 sdb_parser_yyerrorf(YYLTYPE *lval, sdb_parser_yyscan_t scanner, const char *fmt, ...);
64 /* quick access to the current parse tree */
65 #define pt sdb_parser_yyget_extra(scanner)->parsetree
67 /* quick access to the parser mode */
68 #define parser_mode sdb_parser_yyget_extra(scanner)->mode
70 /* quick access to the parser's error buffer */
71 #define errbuf sdb_parser_yyget_extra(scanner)->errbuf
73 #define CK_OOM(p) \
74         do { \
75                 if (! (p)) { \
76                         sdb_parser_yyerror(&yylloc, scanner, YY_("out of memory")); \
77                         YYABORT; \
78                 } \
79         } while (0)
81 #define MODE_TO_STRING(m) \
82         (((m) == SDB_PARSE_DEFAULT) ? "statement" \
83                 : ((m) == SDB_PARSE_COND) ? "condition" \
84                 : ((m) == SDB_PARSE_ARITH) ? "arithmetic expression" \
85                 : "UNKNOWN")
87 %}
89 %pure-parser
90 %lex-param {sdb_parser_yyscan_t scanner}
91 %parse-param {sdb_parser_yyscan_t scanner}
92 %locations
93 %error-verbose
94 %expect 0
95 %name-prefix "sdb_parser_yy"
97 %union {
98         char *str;
99         int integer;
101         sdb_data_t data;
102         sdb_time_t datetime;
104         sdb_llist_t    *list;
105         sdb_ast_node_t *node;
107         struct { char *type; char *id; } metric_store;
110 %start statements
112 %token SCANNER_ERROR
114 %token AND OR IS NOT MATCHING FILTER
115 %token CMP_EQUAL CMP_NEQUAL CMP_REGEX CMP_NREGEX
116 %token CMP_LT CMP_LE CMP_GE CMP_GT ALL ANY IN
117 %token CONCAT
119 %token HOST_T HOSTS_T SERVICE_T SERVICES_T METRIC_T METRICS_T
120 %token ATTRIBUTE_T ATTRIBUTES_T
121 %token NAME_T LAST_UPDATE_T AGE_T INTERVAL_T BACKEND_T VALUE_T
123 %token LAST UPDATE
125 %token START END
127 /* NULL token */
128 %token NULL_T
130 %token FETCH LIST LOOKUP STORE TIMESERIES
132 %token <str> IDENTIFIER STRING
134 %token <data> INTEGER FLOAT
136 %token <datetime> DATE TIME
138 /* Precedence (lowest first): */
139 %left OR
140 %left AND
141 %right NOT
142 %left CMP_EQUAL CMP_NEQUAL
143 %left CMP_LT CMP_LE CMP_GE CMP_GT
144 %nonassoc CMP_REGEX CMP_NREGEX
145 %nonassoc IN
146 %left CONCAT
147 %nonassoc IS
148 %left '+' '-'
149 %left '*' '/' '%'
150 %left '[' ']'
151 %left '(' ')'
152 %left '.'
154 %type <list> statements
155 %type <node> statement
156         fetch_statement
157         list_statement
158         lookup_statement
159         store_statement
160         timeseries_statement
161         matching_clause
162         filter_clause
163         condition comparison
164         expression object_expression
166 %type <integer> object_type object_type_plural
167 %type <integer> field
168 %type <integer> cmp
170 %type <data> data
171         interval interval_elem
172         array array_elem_list
174 %type <datetime> datetime
175         start_clause end_clause
176         last_update_clause
178 %type <metric_store> metric_store_clause
180 %destructor { free($$); } <str>
181 %destructor { sdb_object_deref(SDB_OBJ($$)); } <node>
182 %destructor { sdb_data_free_datum(&$$); } <data>
184 %%
186 statements:
187         statements ';' statement
188                 {
189                         /* only accepted in default parse mode */
190                         if (parser_mode != SDB_PARSE_DEFAULT) {
191                                 sdb_parser_yyerrorf(&yylloc, scanner,
192                                                 YY_("syntax error, unexpected statement, "
193                                                         "expecting %s"), MODE_TO_STRING(parser_mode));
194                                 sdb_object_deref(SDB_OBJ($3));
195                                 YYABORT;
196                         }
198                         if ($3) {
199                                 sdb_llist_append(pt, SDB_OBJ($3));
200                                 sdb_object_deref(SDB_OBJ($3));
201                         }
202                 }
203         |
204         statement
205                 {
206                         /* only accepted in default parse mode */
207                         if (parser_mode != SDB_PARSE_DEFAULT) {
208                                 sdb_parser_yyerrorf(&yylloc, scanner,
209                                                 YY_("syntax error, unexpected statement, "
210                                                         "expecting %s"), MODE_TO_STRING(parser_mode));
211                                 sdb_object_deref(SDB_OBJ($1));
212                                 YYABORT;
213                         }
215                         if ($1) {
216                                 sdb_llist_append(pt, SDB_OBJ($1));
217                                 sdb_object_deref(SDB_OBJ($1));
218                         }
219                 }
220         |
221         condition
222                 {
223                         /* only accepted in condition parse mode */
224                         if (! (parser_mode & SDB_PARSE_COND)) {
225                                 sdb_parser_yyerrorf(&yylloc, scanner,
226                                                 YY_("syntax error, unexpected condition, "
227                                                         "expecting %s"), MODE_TO_STRING(parser_mode));
228                                 sdb_object_deref(SDB_OBJ($1));
229                                 YYABORT;
230                         }
232                         if ($1) {
233                                 sdb_llist_append(pt, SDB_OBJ($1));
234                                 sdb_object_deref(SDB_OBJ($1));
235                         }
236                 }
237         |
238         expression
239                 {
240                         /* only accepted in expression parse mode */
241                         if (! (parser_mode & SDB_PARSE_ARITH)) {
242                                 sdb_parser_yyerrorf(&yylloc, scanner,
243                                                 YY_("syntax error, unexpected expression, "
244                                                         "expecting %s"), MODE_TO_STRING(parser_mode));
245                                 sdb_object_deref(SDB_OBJ($1));
246                                 YYABORT;
247                         }
249                         if ($1) {
250                                 sdb_llist_append(pt, SDB_OBJ($1));
251                                 sdb_object_deref(SDB_OBJ($1));
252                         }
253                 }
254         ;
256 statement:
257         fetch_statement
258         |
259         list_statement
260         |
261         lookup_statement
262         |
263         store_statement
264         |
265         timeseries_statement
266         |
267         /* empty */
268                 {
269                         $$ = NULL;
270                 }
271         ;
273 /*
274  * FETCH host <hostname> [FILTER <condition>];
275  * FETCH <type> <hostname>.<name> [FILTER <condition>];
276  *
277  * Retrieve detailed information about a single object.
278  */
279 fetch_statement:
280         FETCH object_type STRING filter_clause
281                 {
282                         $$ = sdb_ast_fetch_create($2, NULL, $3, $4);
283                         CK_OOM($$);
284                 }
285         |
286         FETCH object_type STRING '.' STRING filter_clause
287                 {
288                         $$ = sdb_ast_fetch_create($2, $3, $5, $6);
289                         CK_OOM($$);
290                 }
291         ;
293 /*
294  * LIST <type> [FILTER <condition>];
295  *
296  * Returns a list of all objects in the store.
297  */
298 list_statement:
299         LIST object_type_plural filter_clause
300                 {
301                         $$ = sdb_ast_list_create($2, $3);
302                         CK_OOM($$);
303                 }
304         ;
306 /*
307  * LOOKUP <type> [MATCHING <condition>] [FILTER <condition>];
308  *
309  * Returns detailed information about objects matching a condition.
310  */
311 lookup_statement:
312         LOOKUP object_type_plural matching_clause filter_clause
313                 {
314                         $$ = sdb_ast_lookup_create($2, $3, $4);
315                         CK_OOM($$);
316                 }
317         ;
319 matching_clause:
320         MATCHING condition { $$ = $2; }
321         |
322         /* empty */ { $$ = NULL; }
324 filter_clause:
325         FILTER condition { $$ = $2; }
326         |
327         /* empty */ { $$ = NULL; }
329 /*
330  * STORE <type> <name>|<host>.<name> [LAST UPDATE <datetime>];
331  * STORE METRIC <host>.<name> STORE <type> <id> [LAST UPDATE <datetime>];
332  * STORE <type> ATTRIBUTE <parent>.<key> <datum> [LAST UPDATE <datetime>];
333  *
334  * Store or update an object in the database.
335  */
336 store_statement:
337         STORE HOST_T STRING last_update_clause
338                 {
339                         $$ = sdb_ast_store_create(SDB_HOST, NULL, 0, NULL,
340                                         $3, $4, NULL, NULL, SDB_DATA_NULL);
341                         CK_OOM($$);
342                 }
343         |
344         STORE SERVICE_T STRING '.' STRING last_update_clause
345                 {
346                         $$ = sdb_ast_store_create(SDB_SERVICE, $3, 0, NULL,
347                                         $5, $6, NULL, NULL, SDB_DATA_NULL);
348                         CK_OOM($$);
349                 }
350         |
351         STORE METRIC_T STRING '.' STRING metric_store_clause last_update_clause
352                 {
353                         $$ = sdb_ast_store_create(SDB_METRIC, $3, 0, NULL,
354                                         $5, $7, $6.type, $6.id, SDB_DATA_NULL);
355                         CK_OOM($$);
356                 }
357         |
358         STORE HOST_T ATTRIBUTE_T STRING '.' STRING data last_update_clause
359                 {
360                         $$ = sdb_ast_store_create(SDB_ATTRIBUTE, $4, 0, NULL,
361                                         $6, $8, NULL, NULL, $7);
362                         CK_OOM($$);
363                 }
364         |
365         STORE SERVICE_T ATTRIBUTE_T STRING '.' STRING '.' STRING data last_update_clause
366                 {
367                         $$ = sdb_ast_store_create(SDB_ATTRIBUTE, $4, SDB_SERVICE, $6,
368                                         $8, $10, NULL, NULL, $9);
369                         CK_OOM($$);
370                 }
371         |
372         STORE METRIC_T ATTRIBUTE_T STRING '.' STRING '.' STRING data last_update_clause
373                 {
374                         $$ = sdb_ast_store_create(SDB_ATTRIBUTE, $4, SDB_METRIC, $6,
375                                         $8, $10, NULL, NULL, $9);
376                         CK_OOM($$);
377                 }
378         ;
380 last_update_clause:
381         LAST UPDATE datetime { $$ = $3; }
382         |
383         /* empty */ { $$ = sdb_gettime(); }
385 metric_store_clause:
386         STORE STRING STRING { $$.type = $2; $$.id = $3; }
387         |
388         /* empty */ { $$.type = $$.id = NULL; }
390 /*
391  * TIMESERIES <host>.<metric> [START <datetime>] [END <datetime>];
392  *
393  * Returns a time-series for the specified host's metric.
394  */
395 timeseries_statement:
396         TIMESERIES STRING '.' STRING start_clause end_clause
397                 {
398                         $$ = sdb_ast_timeseries_create($2, $4, $5, $6);
399                         CK_OOM($$);
400                 }
401         ;
403 start_clause:
404         START datetime { $$ = $2; }
405         |
406         /* empty */ { $$ = sdb_gettime() - SDB_INTERVAL_HOUR; }
408 end_clause:
409         END datetime { $$ = $2; }
410         |
411         /* empty */ { $$ = sdb_gettime(); }
413 /*
414  * Basic expressions.
415  */
417 condition:
418         '(' condition ')'
419                 {
420                         $$ = $2;
421                 }
422         |
423         condition AND condition
424                 {
425                         $$ = sdb_ast_op_create(SDB_AST_AND, $1, $3);
426                         CK_OOM($$);
427                 }
428         |
429         condition OR condition
430                 {
431                         $$ = sdb_ast_op_create(SDB_AST_OR, $1, $3);
432                         CK_OOM($$);
433                 }
434         |
435         NOT condition
436                 {
437                         $$ = sdb_ast_op_create(SDB_AST_NOT, NULL, $2);
438                         CK_OOM($$);
439                 }
440         |
441         comparison
442                 {
443                         $$ = $1;
444                 }
445         ;
447 comparison:
448         expression cmp expression
449                 {
450                         $$ = sdb_ast_op_create($2, $1, $3);
451                         CK_OOM($$);
452                 }
453         |
454         ANY expression cmp expression
455                 {
456                         sdb_ast_node_t *n = sdb_ast_op_create($3, NULL, $4);
457                         CK_OOM(n);
458                         $$ = sdb_ast_iter_create(SDB_AST_ANY, $2, n);
459                         CK_OOM($$);
460                 }
461         |
462         ALL expression cmp expression
463                 {
464                         sdb_ast_node_t *n = sdb_ast_op_create($3, NULL, $4);
465                         CK_OOM(n);
466                         $$ = sdb_ast_iter_create(SDB_AST_ALL, $2, n);
467                         CK_OOM($$);
468                 }
469         |
470         expression IS NULL_T
471                 {
472                         $$ = sdb_ast_op_create(SDB_AST_ISNULL, NULL, $1);
473                         CK_OOM($$);
474                 }
475         |
476         expression IS NOT NULL_T
477                 {
478                         $$ = sdb_ast_op_create(SDB_AST_ISNULL, NULL, $1);
479                         CK_OOM($$);
480                         $$ = sdb_ast_op_create(SDB_AST_NOT, NULL, $$);
481                         CK_OOM($$);
482                 }
483         |
484         expression IN expression
485                 {
486                         $$ = sdb_ast_op_create(SDB_AST_IN, $1, $3);
487                         CK_OOM($$);
488                 }
489         |
490         expression NOT IN expression
491                 {
492                         $$ = sdb_ast_op_create(SDB_AST_IN, $1, $4);
493                         CK_OOM($$);
494                         $$ = sdb_ast_op_create(SDB_AST_NOT, NULL, $$);
495                         CK_OOM($$);
496                 }
497         ;
499 expression:
500         '(' expression ')'
501                 {
502                         $$ = $2;
503                 }
504         |
505         expression '+' expression
506                 {
507                         $$ = sdb_ast_op_create(SDB_AST_ADD, $1, $3);
508                         CK_OOM($$);
509                 }
510         |
511         expression '-' expression
512                 {
513                         $$ = sdb_ast_op_create(SDB_AST_SUB, $1, $3);
514                         CK_OOM($$);
515                 }
516         |
517         expression '*' expression
518                 {
519                         $$ = sdb_ast_op_create(SDB_AST_MUL, $1, $3);
520                         CK_OOM($$);
521                 }
522         |
523         expression '/' expression
524                 {
525                         $$ = sdb_ast_op_create(SDB_AST_DIV, $1, $3);
526                         CK_OOM($$);
527                 }
528         |
529         expression '%' expression
530                 {
531                         $$ = sdb_ast_op_create(SDB_AST_MOD, $1, $3);
532                         CK_OOM($$);
533                 }
534         |
535         expression CONCAT expression
536                 {
537                         $$ = sdb_ast_op_create(SDB_AST_CONCAT, $1, $3);
538                         CK_OOM($$);
539                 }
540         |
541         object_expression
542                 {
543                         $$ = $1;
544                 }
545         |
546         data
547                 {
548                         $$ = sdb_ast_const_create($1);
549                         CK_OOM($$);
550                 }
551         ;
553 object_expression:
554         object_type '.' object_expression
555                 {
556                         $$ = sdb_ast_typed_create($1, $3);
557                         CK_OOM($$);
558                 }
559         |
560         ATTRIBUTE_T '.' object_expression
561                 {
562                         $$ = sdb_ast_typed_create(SDB_ATTRIBUTE, $3);
563                         CK_OOM($$);
564                 }
565         |
566         field
567                 {
568                         $$ = sdb_ast_value_create($1, NULL);
569                         CK_OOM($$);
570                 }
571         |
572         ATTRIBUTE_T '[' STRING ']'
573                 {
574                         $$ = sdb_ast_value_create(SDB_ATTRIBUTE, $3);
575                         CK_OOM($$);
576                 }
577         ;
579 object_type:
580         HOST_T { $$ = SDB_HOST; }
581         |
582         SERVICE_T { $$ = SDB_SERVICE; }
583         |
584         METRIC_T { $$ = SDB_METRIC; }
585         ;
587 object_type_plural:
588         HOSTS_T { $$ = SDB_HOST; }
589         |
590         SERVICES_T { $$ = SDB_SERVICE; }
591         |
592         METRICS_T { $$ = SDB_METRIC; }
593         ;
595 field:
596         NAME_T { $$ = SDB_FIELD_NAME; }
597         |
598         LAST_UPDATE_T { $$ = SDB_FIELD_LAST_UPDATE; }
599         |
600         AGE_T { $$ = SDB_FIELD_AGE; }
601         |
602         INTERVAL_T { $$ = SDB_FIELD_INTERVAL; }
603         |
604         BACKEND_T { $$ = SDB_FIELD_BACKEND; }
605         |
606         VALUE_T { $$ = SDB_FIELD_VALUE; }
607         ;
609 cmp:
610         CMP_EQUAL { $$ = SDB_AST_EQ; }
611         |
612         CMP_NEQUAL { $$ = SDB_AST_NE; }
613         |
614         CMP_REGEX { $$ = SDB_AST_REGEX; }
615         |
616         CMP_NREGEX { $$ = SDB_AST_NREGEX; }
617         |
618         CMP_LT { $$ = SDB_AST_LT; }
619         |
620         CMP_LE { $$ = SDB_AST_LE; }
621         |
622         CMP_GE { $$ = SDB_AST_GE; }
623         |
624         CMP_GT { $$ = SDB_AST_GT; }
625         ;
627 data:
628         STRING { $$.type = SDB_TYPE_STRING; $$.data.string = $1; }
629         |
630         INTEGER { $$ = $1; }
631         |
632         FLOAT { $$ = $1; }
633         |
634         datetime { $$.type = SDB_TYPE_DATETIME; $$.data.datetime = $1; }
635         |
636         interval { $$ = $1; }
637         |
638         array { $$ = $1; }
639         ;
641 datetime:
642         DATE TIME { $$ = $1 + $2; }
643         |
644         DATE { $$ = $1; }
645         |
646         TIME { $$ = $1; }
647         ;
649 interval:
650         interval interval_elem
651                 {
652                         $$.data.datetime = $1.data.datetime + $2.data.datetime;
653                 }
654         |
655         interval_elem { $$ = $1; }
656         ;
658 interval_elem:
659         INTEGER IDENTIFIER
660                 {
661                         sdb_time_t unit = sdb_strpunit($2);
662                         if (! unit) {
663                                 sdb_parser_yyerrorf(&yylloc, scanner,
664                                                 YY_("syntax error, invalid time unit %s"), $2);
665                                 free($2); $2 = NULL;
666                                 YYABORT;
667                         }
668                         free($2); $2 = NULL;
670                         if ($1.data.integer < 0) {
671                                 sdb_parser_yyerror(&yylloc, scanner,
672                                                 YY_("syntax error, negative intervals not supported"));
673                                 YYABORT;
674                         }
676                         $$.type = SDB_TYPE_DATETIME;
677                         $$.data.datetime = (sdb_time_t)$1.data.integer * unit;
678                 }
679         ;
681 array:
682         '[' array_elem_list ']'
683                 {
684                         $$ = $2;
685                 }
686         ;
688 array_elem_list:
689         array_elem_list ',' data
690                 {
691                         size_t elem_size = sdb_data_sizeof($3.type);
693                         if (($3.type & SDB_TYPE_ARRAY) || (($1.type & 0xff) != $3.type)) {
694                                 sdb_parser_yyerrorf(&yylloc, scanner, YY_("syntax error, "
695                                                 "cannot use element of type %s in array of type %s"),
696                                                 SDB_TYPE_TO_STRING($3.type),
697                                                 SDB_TYPE_TO_STRING($1.type));
698                                 sdb_data_free_datum(&$1);
699                                 sdb_data_free_datum(&$3);
700                                 YYABORT;
701                         }
703                         $$ = $1;
704                         $$.data.array.values = realloc($$.data.array.values,
705                                         ($$.data.array.length + 1) * elem_size);
706                         CK_OOM($$.data.array.values);
708                         memcpy((char *)$$.data.array.values + $$.data.array.length * elem_size,
709                                         &$3.data, elem_size);
710                         ++$$.data.array.length;
711                 }
712         |
713         data
714                 {
715                         size_t elem_size = sdb_data_sizeof($1.type);
717                         if ($1.type & SDB_TYPE_ARRAY) {
718                                 sdb_parser_yyerrorf(&yylloc, scanner, YY_("syntax error, "
719                                                 "cannot construct array of type %s"),
720                                                 SDB_TYPE_TO_STRING($1.type));
721                                 sdb_data_free_datum(&$1);
722                                 YYABORT;
723                         }
725                         $$ = $1;
726                         $$.type |= SDB_TYPE_ARRAY;
727                         $$.data.array.values = malloc(elem_size);
728                         CK_OOM($$.data.array.values);
730                         memcpy($$.data.array.values, &$1.data, elem_size);
731                         $$.data.array.length = 1;
732                 }
733         ;
735 %%
737 void
738 sdb_parser_yyerror(YYLTYPE *lval, sdb_parser_yyscan_t scanner, const char *msg)
740         sdb_log(SDB_LOG_ERR, "parser: parse error: %s", msg);
741         sdb_strbuf_sprintf(errbuf, "%s", msg);
742 } /* sdb_parser_yyerror */
744 void
745 sdb_parser_yyerrorf(YYLTYPE *lval, sdb_parser_yyscan_t scanner, const char *fmt, ...)
747         va_list ap, aq;
748         va_start(ap, fmt);
749         va_copy(aq, ap);
750         sdb_vlog(SDB_LOG_ERR, fmt, ap);
751         sdb_strbuf_vsprintf(errbuf, fmt, aq);
752         va_end(ap);
753 } /* sdb_parser_yyerrorf */
755 /* vim: set tw=78 sw=4 ts=4 noexpandtab : */