head 1.13;
access;
symbols
L2_0_9_13:1.13
FSL_1_7_0:1.13
L2_0_9_12:1.13
LMTP2NNTP_1_4_1:1.13
LMTP2NNTP_1_4_0:1.13
FSL_1_6_1:1.13
L2_0_9_11:1.13
FSL_1_6_0:1.12
FSL_1_6b2:1.12
L2_0_9_10:1.12
FSL_1_6b1:1.12
L2_0_9_9:1.12
LMTP2NNTP_1_3_0:1.11
LMTP2NNTP_1_3b2:1.11
LMTP2NNTP_1_3b1:1.11
LMTP2NNTP_1_3a3:1.11
FSL_1_5_0:1.11
LMTP2NNTP_1_3a2:1.11
FSL_1_5a3:1.11
LMTP2NNTP_1_3a1:1.11
FSL_1_5a2:1.11
L2_0_9_8:1.11
FSL_1_5a1:1.11
L2_0_9_7:1.11
L2_0_9_6:1.11
FSL_1_4_0:1.11
FSL_1_4b1:1.11
L2_0_9_5:1.11
FSL_1_4a1:1.11
FSL_1_3_0:1.11
FSL_1_3b1:1.11
L2_0_9_4:1.11
FSL_1_2_1:1.11
L2_0_9_3:1.11
FSL_1_2_0:1.11
L2_0_9_2:1.11
FSL_1_1_0:1.11
FSL_1_1b1:1.11
WORKOFF:1.11.0.2
WORKOFF_BP:1.11
FSL_1_0_8:1.11
LMTP2NNTP_1_2_0:1.11
LMTP2NNTP_1_2b4:1.11
LMTP2NNTP_1_2b3:1.11
LMTP2NNTP_1_2b2:1.11
LMTP2NNTP_1_2b1:1.10
LMTP2NNTP_1_2a8:1.10
LMTP2NNTP_1_2a7:1.10
FSL_1_0_7:1.10
FSL_1_0_6:1.8
FSL_1_0_5:1.8
FSL_1_0_4:1.8
L2_0_9_1:1.8
FSL_1_0_3:1.8
LMTP2NNTP_1_2a6:1.8
FSL_1_0_2:1.8
FSL_1_0_1:1.8
FSL_1_0_0:1.8
FSL_0_9_0:1.8
L2_0_9_0:1.8
FSL_0_1_12:1.7
FSL_0_1_11:1.7
FSL_0_1_10:1.7
FSL_0_1_9:1.7
FSL_0_1_8:1.7
FSL_0_1_7:1.7
FSL_0_1_6:1.7
FSL_0_1_5:1.7
FSL_0_1_1:1.6
LMTP2NNTP_1_2a5:1.6
LMTP2NNTP_1_2a4:1.6
LMTP2NNTP_1_2a3:1.6
LMTP2NNTP_1_2a1:1.4;
locks; strict;
comment @ * @;
1.13
date 2005.10.03.08.08.11; author rse; state Exp;
branches;
next 1.12;
1.12
date 2005.01.24.15.03.18; author rse; state Exp;
branches;
next 1.11;
1.11
date 2003.02.11.07.51.27; author rse; state Exp;
branches;
next 1.10;
1.10
date 2003.01.06.11.41.52; author rse; state Exp;
branches;
next 1.9;
1.9
date 2003.01.06.11.19.45; author rse; state Exp;
branches;
next 1.8;
1.8
date 2002.07.30.19.08.25; author rse; state Exp;
branches;
next 1.7;
1.7
date 2002.07.25.09.13.13; author rse; state Exp;
branches;
next 1.6;
1.6
date 2002.01.02.17.07.38; author rse; state Exp;
branches;
next 1.5;
1.5
date 2001.12.14.12.42.07; author rse; state Exp;
branches;
next 1.4;
1.4
date 2001.11.08.21.58.00; author rse; state Exp;
branches;
next 1.3;
1.3
date 2001.11.08.09.28.35; author rse; state Exp;
branches;
next 1.2;
1.2
date 2001.11.07.17.01.05; author rse; state Exp;
branches;
next 1.1;
1.1
date 2001.11.07.16.17.09; author rse; state Exp;
branches;
next ;
desc
@@
1.13
log
@Adjust copyright messages for new year 2005.
@
text
@%{
/*
** OSSP l2 - Flexible Logging
** Copyright (c) 2001-2005 Cable & Wireless
** Copyright (c) 2001-2005 The OSSP Project
** Copyright (c) 2001-2005 Ralf S. Engelschall
**
** This file is part of OSSP l2, a flexible logging library which
** can be found at http://www.ossp.org/pkg/lib/l2/.
**
** Permission to use, copy, modify, and distribute this software for
** any purpose with or without fee is hereby granted, provided that
** the above copyright notice and this permission notice appear in all
** copies.
**
** THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
** USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
** OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
** OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
** SUCH DAMAGE.
**
** l2_spec_parse.y: GNU Bison (Yacc-style) parser specification
**
** ATTENTION: This requires GNU Bison 1.875 or newer!
*/
#include "l2.h" /* for l2_xxx() API */
#include "l2_spec.h" /* for l2_spec_ctx_t */
/* make sure yyparse() accepts a context pointer and
passes through its inlined scanner context to yylex() */
#define CTX ((l2_spec_ctx_t *)ctx)
#define YYPARSE_PARAM ctx
#define YYLEX_PARAM CTX->yyscan
/* provide an explicit prototype for the yylex() function but use
"void *" instead of correct type because at this point in the
generation process we have the types still not available */
extern int yylex(/*YYSTYPE*/ void *lvalp,
/*YYLTYPE*/ void *llocp, l2_spec_ctx_t *ctx);
/* generate verbose error messages and remember them inside the context */
#undef yyerror
#define yyerror(msg) \
l2_spec_error(CTX, L2_ERR_SYN, &yylloc, msg)
/* scanner state transition */
extern void l2_spec_scan_push(l2_spec_ctx_t *, const char *state);
extern void l2_spec_scan_pop(l2_spec_ctx_t *);
%}
/* parser options */
%error-verbose
%pure_parser
%locations
%defines
/* the YYSTYPE: parser token value */
%union {
char *cpValue;
l2_channel_t *chChannel;
unsigned int nLevels;
}
/* assign YYSTYPE elements to parser rules */
%type stream
%type streams
%type channel
%type channel_cons
%type channel_level
%type channel_level_mask
/* list of scanner tokens */
%token T_ID
%token T_PARAM
%token T_STRING
%token T_OP_ARROW
/* operator association */
%right T_OP_ARROW
/* grammar start rule
(technically redundant but explicit to be sure) */
%start tree
%%
/* channel tree */
tree
: stream {
CTX->ch = $1;
}
;
/* stream of channels */
stream
: /* empty */ {
l2_channel_t *ch;
if ((CTX->rv = l2_channel_create(&ch, CTX->env, "null")) != L2_OK) {
l2_spec_error(CTX, CTX->rv, &yylloc, "failed to create channel 'null'");
YYERROR;
}
$$ = ch;
}
| channel {
$$ = $1;
}
| channel T_OP_ARROW stream {
$$ = $1;
if ((CTX->rv = l2_channel_link($1, L2_LINK_CHILD, $3, NULL)) != L2_OK) {
l2_spec_error(CTX, CTX->rv, &yylloc, "unable to link parent with child");
YYERROR;
}
}
| channel T_OP_ARROW '{' streams '}' {
$$ = $1;
if ((CTX->rv = l2_channel_link($1, L2_LINK_CHILD, $4, NULL)) != L2_OK) {
l2_spec_error(CTX, CTX->rv, &yylloc, "unable to link parent with child list");
YYERROR;
}
}
;
/* list of sibling streams */
streams
: stream {
$$ = $1;
}
| stream ';' streams {
$$ = $1;
if ((CTX->rv = l2_channel_link($1, L2_LINK_SIBLING, $3, NULL)) != L2_OK) {
l2_spec_error(CTX, CTX->rv, &yylloc, "unable to link childs together");
YYERROR;
}
}
;
/* channel */
channel
: channel_level '/' channel_level ':' channel_cons {
$$ = $5;
if ((CTX->rv = l2_channel_levels($5, $1, $3)) != L2_OK) {
l2_spec_error(CTX, CTX->rv, &yylloc, "failed to set channel write and flush levels");
YYERROR;
}
}
| channel_level ':' channel_cons {
$$ = $3;
if ((CTX->rv = l2_channel_levels($3, $1, L2_LEVEL_NONE)) != L2_OK) {
l2_spec_error(CTX, CTX->rv, &yylloc, "failed to set channel write levels");
YYERROR;
}
}
| channel_cons {
$$ = $1;
}
;
/* channel level */
channel_level
: T_ID {
unsigned int levelmask;
if ((CTX->rv = l2_util_s2l($1, strlen($1), ',', &levelmask)) != L2_OK) {
l2_spec_error(CTX, CTX->rv, &yylloc, "invalid level '%s'", $1);
free($1);
YYERROR;
}
free($1);
$$ = L2_LEVEL_UPTO(levelmask);
}
| '(' channel_level_mask ')' {
$$ = $2;
}
;
/* channel level mask */
channel_level_mask
: T_ID {
unsigned int levelmask;
if ((CTX->rv = l2_util_s2l($1, strlen($1), ',', &levelmask)) != L2_OK) {
l2_spec_error(CTX, CTX->rv, &yylloc, "invalid level '%s'", $1);
free($1);
YYERROR;
}
free($1);
$$ = levelmask;
}
| T_ID '|' channel_level_mask {
unsigned int levelmask;
if ((CTX->rv = l2_util_s2l($1, strlen($1), ',', &levelmask)) != L2_OK) {
l2_spec_error(CTX, CTX->rv, &yylloc, "invalid level '%s'", $1);
free($1);
YYERROR;
}
free($1);
$$ = levelmask | $3;
}
;
/* channel constructor */
channel_cons
: T_ID {
l2_channel_t *ch;
if ((CTX->rv = l2_channel_create(&ch, CTX->env, $1)) != L2_OK) {
l2_spec_error(CTX, CTX->rv, &yylloc, "failed to create channel '%s'", $1);
free($1);
YYERROR;
}
free($1);
$$ = ch;
/* provide channel to channel_param rule below because it does
not know where on the token stack our $$ is because it is a
sub-rule of the recursive channel_param_list rule and hence
cannot use "$-n". */
CTX->chTmp = ch;
}
channel_params {
$$ = $2; /* pass-through result */
CTX->chTmp = NULL;
}
;
/* channel parameters */
channel_params
: /* empty */
| '(' ')'
| '(' channel_param_list ')'
;
/* channel parameter list */
channel_param_list
: channel_param
| channel_param ',' channel_param_list
;
/* channel parameter */
channel_param
: T_ID '=' { l2_spec_scan_push(CTX, "SS_PARAM"); } T_PARAM { l2_spec_scan_pop(CTX); } {
if ((CTX->rv = l2_channel_configure(CTX->chTmp, "%s=\"%s\"", $1, $4)) != L2_OK) {
l2_spec_error(CTX, CTX->rv, &yylloc, "failed to configure channel with '%s=\"%s\"'", $1, $4);
free($1);
free($4);
YYERROR;
}
free($1);
free($4);
}
;
%%
@
1.12
log
@Adjust copyright messages for new year 2005.
@
text
@d4 3
a6 3
** Copyright (c) 2001-2004 Cable & Wireless
** Copyright (c) 2001-2004 The OSSP Project
** Copyright (c) 2001-2004 Ralf S. Engelschall
@
1.11
log
@fix all dmalloc-detected memory leaks
@
text
@d4 3
a6 3
** Copyright (c) 2001-2003 Cable & Wireless Deutschland GmbH
** Copyright (c) 2001-2003 The OSSP Project (http://www.ossp.org/)
** Copyright (c) 2001-2003 Ralf S. Engelschall
@
1.10
log
@- remove trailing whitespaces
- adjust copyright messages
- consistently use "OSSP l2"
- consistently talk about "Flexible Logging"
- use standard OSSP ASCII-art
@
text
@d172 1
d175 1
d189 1
d192 1
d199 1
d202 1
d213 1
d216 1
d248 2
d252 2
@
1.9
log
@upgrade to the latest Flex & Bison & Autoconf combo
@
text
@d3 4
a6 3
** OSSP l2 - Logging Library
** Copyright (c) 2001-2002 The OSSP Project (http://www.ossp.org/)
** Copyright (c) 2001-2002 Cable & Wireless Deutschland (http://www.cw.com/de/)
d8 1
a8 1
** This file is part of OSSP L2, a flexible logging library which
d43 2
a44 2
/* provide an explicit prototype for the yylex() function but use
"void *" instead of correct type because at this point in the
d46 1
a46 1
extern int yylex(/*YYSTYPE*/ void *lvalp,
d89 1
a89 1
/* grammar start rule
d97 1
a97 1
: stream {
d100 1
a100 1
;
d112 2
a113 2
| channel {
$$ = $1;
d115 2
a116 2
| channel T_OP_ARROW stream {
$$ = $1;
d122 2
a123 2
| channel T_OP_ARROW '{' streams '}' {
$$ = $1;
d133 2
a134 2
: stream {
$$ = $1;
d136 2
a137 2
| stream ';' streams {
$$ = $1;
d147 2
a148 2
: channel_level '/' channel_level ':' channel_cons {
$$ = $5;
d154 2
a155 2
| channel_level ':' channel_cons {
$$ = $3;
d161 1
a161 1
| channel_cons {
d168 1
a168 1
: T_ID {
d176 2
a177 2
| '(' channel_level_mask ')' {
$$ = $2;
d182 2
a183 2
channel_level_mask
: T_ID {
d191 1
a191 1
| T_ID '|' channel_level_mask {
d197 1
a197 1
$$ = levelmask | $3;
d202 2
a203 2
channel_cons
: T_ID {
d214 1
a214 1
CTX->chTmp = ch;
d216 1
a216 1
channel_params {
d223 1
a223 1
channel_params
d230 1
a230 1
channel_param_list
d234 1
a234 1
d236 2
a237 2
channel_param
: T_ID '=' { l2_spec_scan_push(CTX, "SS_PARAM"); } T_PARAM { l2_spec_scan_pop(CTX); } {
d244 1
a244 1
@
1.8
log
@polish for release
@
text
@d30 1
a30 1
** ATTENTION: This requires GNU Bison 1.30 or newer!
a51 1
#define YYERROR_VERBOSE
d59 1
@
1.7
log
@allow empty stream (means a 'null' channel)
@
text
@d3 1
a3 1
** L2 - OSSP Logging Library
d8 1
a8 1
** can be found at http://www.ossp.org/pkg/l2/.
@
1.6
log
@bump copyright year
@
text
@d103 9
a111 1
: channel {
@
1.5
log
@Whoohooo! Add support for location tracking. This way the reported
errors contain line, column, context and found/expected information.
Now the specification parser is full featured...
@
text
@d4 2
a5 2
** Copyright (c) 2001 The OSSP Project (http://www.ossp.org/)
** Copyright (c) 2001 Cable & Wireless Deutschland (http://www.cw.com/de/)
@
1.4
log
@Enhance the specification parser to be more smart in parsing parameter
values in order to remove the burden on the user to provide massive
syntactic sugar.
One now can name=value or name="value" or even name=va"lu"e, i.e., do
not require quotation marks. But if they are present they can be used to
introduce spaces or special characters with \xXX notation.
@
text
@d51 1
a51 2
do { l2_env_errorinfo(CTX->env, L2_ERR_ARG, "%s", msg); \
CTX->rv = L2_ERR_ARG; } while (0)
d109 1
a109 1
l2_env_errorinfo(CTX->env, CTX->rv, "unable to link parent with child");
d116 1
a116 1
l2_env_errorinfo(CTX->env, CTX->rv, "unable to link parent with child list");
d130 1
a130 1
l2_env_errorinfo(CTX->env, CTX->rv, "unable to link childs together");
d141 1
a141 1
l2_env_errorinfo(CTX->env, CTX->rv, "failed to set channel write and flush levels");
d148 1
a148 1
l2_env_errorinfo(CTX->env, CTX->rv, "failed to set channel write levels");
d162 1
a162 1
l2_env_errorinfo(CTX->env, CTX->rv, "invalid level '%s'", $1);
d177 1
a177 1
l2_env_errorinfo(CTX->env, CTX->rv, "invalid level '%s'", $1);
d185 1
a185 1
l2_env_errorinfo(CTX->env, CTX->rv, "invalid level '%s'", $1);
d197 1
a197 1
l2_env_errorinfo(CTX->env, CTX->rv, "failed to create channel '%s'", $1);
d216 1
d222 1
a222 2
: /* empty */
| channel_param
d230 1
a230 1
l2_env_errorinfo(CTX->env, CTX->rv, "failed to configure channel with '%s=\"%s\"'", $1, $4);
@
1.3
log
@code cleanups and documentation
@
text
@d54 4
d82 1
d229 3
a231 3
: T_ID '=' T_STRING {
if ((CTX->rv = l2_channel_configure(CTX->chTmp, "%s=\"%s\"", $1, $3)) != L2_OK) {
l2_env_errorinfo(CTX->env, CTX->rv, "failed to configure channel with '%s=\"%s\"'", $1, $3);
@
1.2
log
@more cleanups
@
text
@d56 1
a56 1
/* parser options */
d61 1
a61 1
/* the YYSTYPE: parser token value */
d68 1
a68 1
/* assign YYSTYPE elements to parser rules */
d72 3
a74 3
%type channel_spec
%type channel_mask
%type channel_levels
d76 1
a76 1
/* list of scanner tokens */
d81 1
a81 1
/* operator association */
d84 2
a85 1
/* grammar start rule */
d90 131
a220 113
/* channel tree */
tree : stream
{ CTX->ch = $1; }
;
/* stream of channels */
stream : channel
{
$$ = $1;
}
| channel T_OP_ARROW stream
{
$$ = $1;
if ((CTX->rv = l2_channel_link($1, L2_LINK_CHILD, $3, NULL)) != L2_OK) {
l2_env_errorinfo(CTX->env, CTX->rv, "unable to link parent with child");
YYERROR;
}
}
| channel T_OP_ARROW '{' streams '}'
{
$$ = $1;
if ((CTX->rv = l2_channel_link($1, L2_LINK_CHILD, $4, NULL)) != L2_OK) {
l2_env_errorinfo(CTX->env, CTX->rv, "unable to link parent with child list");
YYERROR;
}
}
;
/* list of sibling streams */
streams : stream
{ $$ = $1; }
| stream ';' streams
{ $$ = $1; l2_channel_link($1, L2_LINK_SIBLING, $3, NULL); }
;
/* single channel */
channel : channel_mask '/' channel_mask ':' channel_spec
{ $$ = $5; l2_channel_levels($5, $1, $3); }
| channel_mask ':' channel_spec
{ $$ = $3; l2_channel_levels($3, $1, L2_LEVEL_NONE); }
| channel_spec
{ $$ = $1; }
;
/* channel write or flush mask */
channel_mask : T_ID
{
unsigned int levelmask;
if ((CTX->rv = l2_util_s2l($1, strlen($1), ',', &levelmask)) != L2_OK) {
l2_env_errorinfo(CTX->env, CTX->rv, "invalid level '%s'", $1);
YYERROR;
}
$$ = L2_LEVEL_UPTO(levelmask);
}
| '(' channel_levels ')'
{
$$ = $2;
}
;
/* list of channels in a mask */
channel_levels : T_ID
{
unsigned int levelmask;
if ((CTX->rv = l2_util_s2l($1, strlen($1), ',', &levelmask)) != L2_OK) {
l2_env_errorinfo(CTX->env, CTX->rv, "invalid level '%s'", $1);
YYERROR;
}
$$ = levelmask;
}
| T_ID '|' channel_levels
{
unsigned int levelmask;
if ((CTX->rv = l2_util_s2l($1, strlen($1), ',', &levelmask)) != L2_OK) {
l2_env_errorinfo(CTX->env, CTX->rv, "invalid level '%s'", $1);
YYERROR;
}
$$ = levelmask | $3;
}
;
/* specifcation of a single channel */
channel_spec : T_ID
{
l2_channel_t *ch;
if ((CTX->rv = l2_channel_create(&ch, CTX->env, $1)) != L2_OK) {
l2_env_errorinfo(CTX->env, CTX->rv, "failed to create channel '%s'", $1);
YYERROR;
}
$$ = ch;
/* provide channel to channel_param rule below
because it does not know where on the token stack
our $$ is because it is a sub-rule of the recursive
channel_param_list rule and hence cannot use
"$-n". */
CTX->chTmp = ch;
}
channel_params
{
$$ = $2; /* pass result */
CTX->chTmp = NULL;
}
;
/* channel parameters */
channel_params : /* empty */
| '(' channel_param_list ')'
;
channel_param_list : /* empty */
| channel_param
| channel_param ',' channel_param_list
;
d222 9
a230 8
channel_param : T_ID '=' T_STRING
{
if ((CTX->rv = l2_channel_configure(CTX->chTmp, "%s=\"%s\"", $1, $3)) != L2_OK) {
l2_env_errorinfo(CTX->env, CTX->rv, "failed to configure channel with '%s=\"%s\"'", $1, $3);
YYERROR;
}
}
;
@
1.1
log
@HEADS UP, guys:
Here comes the first cut for the long-awaited channel tree specification
parser and channel tree builder. This certainly makes L2 finally _THE_ killer
library in the logging field. Woohooo... and the crowd goes wild!
It allows one to build an arbitrary complex logging channel tree
out of a single textual specification. An example follows:
noop -> {
prefix(prefix="[%d-%m-%Y/%H:%M:%S] %L test[%P]: ", timezone="local")
-> filter(regex="hecking", negate="0")
-> buffer(size="800")
-> file(path="l2_test.log",append="1",perm="420");
error: syslog(ident="L2-Test", facility="user",
remotehost="en1", logpid="1", target="remote");
panic: smtp(rcpt="rse@@engelschall.com", host="en1", port="25"
}
This allows one to log nice timestamp-prefixed messages containing the
word "hecking" to a buffered file. Additionally if the message has a
level higher or equal to "error" it also logs it remotely via UDP to the
syslogd on en1. And additionally if the message is a panic message, it
is also sent out as an Email via SMTP to the MTA on en1.
Ever thought a C library has to be dull and simple? ;)
PS: This stuff certainly needs more polishing and cleanup and also
a few things I'll enhance in the future (for instance to remove the
restriction that parameter values have to be in quotation marks, etc.).
@
text
@d33 1
a33 2
#include "l2.h" /* for l2_xxx() */
#include "l2_p.h" /* for l2_channel_t and l2_env_t internals */
d42 3
a44 1
/* provide an explicit prototype for the yylex() function */
d51 2
a52 4
do { \
l2_env_errorinfo(CTX->env, L2_ERR_ARG, "%s", msg); \
CTX->rv = L2_ERR_ARG; \
} while (0)
a53 2
#define YYDEBUG 1
d56 1
d61 1
d68 12
a79 6
%type stream
%type streams
%type channel
%type channel_spec
%type channel_mask
%type channel_levels
d81 2
a82 2
%token T_ID
%token T_STRING
d84 2
a85 2
%token T_OP_ARROW
%left T_OP_ARROW
d89 2
a90 1
root : stream
d94 1
d103 1
a103 3
l2_env_errorinfo(CTX->env, CTX->rv,
"unable to link parent '%s' with child '%s'",
$1->handler.name, $3->handler.name);
d111 1
a111 3
l2_env_errorinfo(CTX->env, CTX->rv,
"unable to link parent '%s' with child list",
$1->handler.name);
d117 1
d124 1
d133 1
d144 3
a146 1
{ $$ = $2; }
d149 1
d170 1
d188 1
a188 1
$$ = $2;
d193 1
@