diff --git a/src/lexer.l b/src/lexer.l index 58039a3..40265fe 100644 --- a/src/lexer.l +++ b/src/lexer.l @@ -29,16 +29,20 @@ typedef struct { int yyerrcnt; - /** pointer to the currently active string delimiter character in bison_args */ - char *sdel_ptr; + /** the currently active string delimiter character */ + char sdel; - /** pointer to the currently active string escape character in bison_args */ - char *sesc_ptr; + /** the currently active string escape character */ + char sesc; } pass_to_flex; - -void begin_speedmode(void *yyscanner); +/* + * Valid characters to be used as string delimiters. Note that the + * following list must correspond to the SDELIM definition below. + */ +#define LEX_SDELIM "\"~'`!@%&*=:;<>?/|.\\" +#define LEX_SDELIM_RECOMMENDED "\"~'!|" /** @@ -73,6 +77,8 @@ static void report_state_char(char *symbol, char c, char *expected_state_str); static void report_state(char *symbol, char *text, char *expected_state_str); +static int change_string_delimiters(pass_to_flex *extra, char *delim_expr); + %} @@ -91,17 +97,16 @@ static void report_state(char *symbol, char *text, char *expected_state_str); %option yylineno -%x DELWORD -%x PARENT +%x BOX %x SAMPLE -%x SPEEDMODE -%s ELASTIC -%s SHAPES +%x SHAPES +%x ELASTIC +%x DELIMSPEC +%x PARENT PWORD [a-zA-ZäöüÄÖÜ][a-zA-Z0-9\-_üäöÜÄÖß]* -PWHITE [\n \r\t] -PBOX Box +PWHITE [ \t\r\n] SDELIM [\"~\'`!@\%\&\*=:;<>\?/|\.\\] PPARENT parent PFILENAME [^\r\n]+ @@ -109,38 +114,39 @@ PFILENAME [^\r\n]+ %% - -[ \r\t] /* ignore whitespace */ - -\n {} - - -[^ \t\r\n]+ { /* - * String delimiter spec - like WORD, but allow any character + * Precedence of rules: + * - The rule that matches the most text wins. + * - If two rules match the same amount of text, the one defined first (further up) wins. + */ + + +{PWHITE} /* ignore whitespace */ + +[^ \t\r\n]+ { + /* + * String delimiter spec - like WORD, but allow any character */ yylval->s = (char *) strdup (yytext); - if (yylval->s == NULL) { - perror (PROJECT); - exit (EXIT_FAILURE); + BEGIN(BOX); + report_state("YDELIMS", yytext, "INITIAL"); + if (change_string_delimiters(yyextra, yylval->s) != 0) { + return YUNREC; } - BEGIN(INITIAL); - report_state("YDELWOR", yytext, "INITIAL"); - return YDELWORD; + return YDELIMSPEC; } -{SDELIM}.*$ { +{SDELIM}.*$ { /* - * Strings -- first match everything starting from a potential - * string delimiter until the end of the line. We will give back what - * we don't need and also detect unterminated strings. + * Strings -- first match everything starting from a potential string delimiter until the end of the line. We + * will give back what we don't need and also detect unterminated strings. Strings always end on the same line. */ char *p; int rest_len = yyleng - 1; /* length of string pointed to by p */ int qcnt = 0; /* esc char count in current string */ - if (yytext[0] != *(yyextra->sdel_ptr)) { + if (yytext[0] != yyextra->sdel) { REJECT; /* that was not our delimiter */ } @@ -152,14 +158,14 @@ PFILENAME [^\r\n]+ p = yylval->s; while (*p) { - if (*p == *(yyextra->sesc_ptr)) { + if (*p == yyextra->sesc) { memmove (p, p+1, rest_len); /* incl. '\0' */ ++qcnt; --rest_len; if (*p == '\0') break; } - else if (*p == *(yyextra->sdel_ptr)) { + else if (*p == yyextra->sdel) { *p = '\0'; yyless ((p-yylval->s)+2+qcnt); /* string plus quotes */ #ifdef LEXER_DEBUG @@ -177,7 +183,7 @@ PFILENAME [^\r\n]+ } -{PPARENT} { +{PPARENT} { BEGIN(PARENT); report_state("YPARENT", yytext, "PARENT"); return YPARENT; @@ -204,19 +210,18 @@ PFILENAME [^\r\n]+ report_state(" NL", "", "INITIAL"); } -Sample { + +Sample { BEGIN(SAMPLE); report_state("YSAMPLE", yytext, "SAMPLE"); return YSAMPLE; } - \n { if (yyleng > 1) yymore(); } - ^[ \t]*ends[ \t\r]*$ { char *p = yytext + yyleng -1; size_t len; /* length of sample */ @@ -239,8 +244,6 @@ Sample { btrim (yylval->s, &len); if (len > 0) { strcat (yylval->s, "\n"); /* memory was allocated with strdup */ - BEGIN(INITIAL); - report_state(" STRING", yylval->s, "INITIAL"); return STRING; } else { @@ -252,53 +255,56 @@ Sample { } } - . yymore(); - -ends[ \t\r]*$ { - #ifdef LEXER_DEBUG - fprintf (stderr, "YENDSAM: %s\n", yytext); - #endif +ends[ \t\r]*$ { + /* reached because the other rule pushes it back so a proper end token can be returned */ + BEGIN(BOX); + report_state("YENDSAM", yytext, "BOX"); return YENDSAMPLE; } -Tags { +Tags { #ifdef LEXER_DEBUG fprintf (stderr, " YTAGS: %s\n", yytext); #endif return YTAGS; } -Elastic { +Elastic { BEGIN(ELASTIC); report_state("YELASTC", yytext, "ELASTIC"); return YELASTIC; } -Shapes { +Shapes { BEGIN(SHAPES); report_state("YSHAPES", yytext, "SHAPES"); return YSHAPES; } -{PBOX} { - #ifdef LEXER_DEBUG - fprintf (stderr, " YBOX: %s\n", yytext); - #endif +Box { + BEGIN(BOX); + report_state(" YBOX", yytext, "BOX"); yyextra->yyerrcnt = 0; + change_string_delimiters(yyextra, "\\\""); return YBOX; } -Replace { return YREPLACE; } -Reverse { return YREVERSE; } -Padding { return YPADDING; } -End { return YEND; } -To { return YTO; } -With { return YWITH; } -Global { yylval->c = 'g'; return YRXPFLAG; } -Once { yylval->c = 'o'; return YRXPFLAG; } +Replace { return YREPLACE; } +Reverse { return YREVERSE; } +Padding { return YPADDING; } +To { return YTO; } +With { return YWITH; } +Global { yylval->c = 'g'; return YRXPFLAG; } +Once { yylval->c = 'o'; return YRXPFLAG; } +End { + BEGIN(INITIAL); + report_state(" YEND", yytext, "INITIAL"); + change_string_delimiters(yyextra, "\\\""); + return YEND; +} nw { yylval->shape = NW; return SHAPE; } @@ -319,21 +325,21 @@ Once { yylval->c = 'o'; return YRXPFLAG; } wnw { yylval->shape = WNW; return SHAPE; } \) { - BEGIN(INITIAL); - report_state_char("SYMBOL", yytext[0], "INITIAL"); + BEGIN(BOX); + report_state_char("SYMBOL", yytext[0], "BOX"); return yytext[0]; } \} { - BEGIN(INITIAL); - report_state_char("SYMBOL", yytext[0], "INITIAL"); + BEGIN(BOX); + report_state_char("SYMBOL", yytext[0], "BOX"); return yytext[0]; } -author|designer|created|revision|revdate|indent { +author|designer|created|revision|revdate|indent { /* - * general key words + * general key words */ #ifdef LEXER_DEBUG fprintf (stderr, "KEYWORD: %s\n", yytext); @@ -347,17 +353,20 @@ author|designer|created|revision|revdate|indent { } -Delimiter|Delim { +Delimiter|Delim { /* - * Change string delimiting characters + * Change string delimiting characters */ - BEGIN(DELWORD); - report_state("YCHGDEL", yytext, "DELWORD"); + BEGIN(DELIMSPEC); + report_state("YCHGDEL", yytext, "DELIMSPEC"); return YCHGDEL; } -{PWORD} { +{PWORD} { + /* + * a free-floating word which is not a string, i.e. it does not have delimiting characters + */ #ifdef LEXER_DEBUG fprintf (stderr, " WORD: %s\n", yytext); #endif @@ -370,7 +379,7 @@ Delimiter|Delim { } -[\+-]?[0-9]+ { +[\+-]?[0-9]+ { #ifdef LEXER_DEBUG fprintf (stderr, "YNUMBER: %s\n", yytext); #endif @@ -379,7 +388,7 @@ Delimiter|Delim { } -[,(){}] { +[,(){}] { #ifdef LEXER_DEBUG fprintf (stderr, " SYMBOL: \'%c\'\n", yytext[0]); #endif @@ -387,7 +396,7 @@ Delimiter|Delim { } -#.*$ { +#.*$ { /* ignore comments */ #ifdef LEXER_DEBUG fprintf (stderr, "COMMENT: %s\n", yytext+1); @@ -395,25 +404,15 @@ Delimiter|Delim { } -. { - if ((yyextra->yyerrcnt)++ < LEX_MAX_WARN) - yyerror(NULL, "Unrecognized input char \'%s\'", yytext); +. { + /* a character that made no sense where it was encountered. Let the parser handle it. */ + #ifdef LEXER_DEBUG + fprintf (stderr, " YUNREC: \'%c\'\n", yytext[0]); + #endif return YUNREC; } -{PBOX}{PWHITE}+{PWORD}|{PPARENT} { - /* end speedmode, but then give back the whole match so BOX or PARENT can be started properly */ - yyless(0); - BEGIN(INITIAL); - report_state(" STATUS", "", "INITIAL"); -} - -\n {} - -. /* ignore anything else */ - - %% @@ -433,19 +432,10 @@ void inflate_inbuf(void *yyscanner, const char *configfile) -void begin_speedmode(void *yyscanner) -{ - struct yyguts_t *yyg = (struct yyguts_t *) yyscanner; - BEGIN(SPEEDMODE); - report_state(" STATUS", "begin_speedmode()", "SPEEDMODE"); -} - - - static void report_state_char(char *symbol, char c, char *expected_state_str) { char *s = (char *) malloc(4); - sprintf(s, "'%c'", c); + sprintf(s, "'%c'", c >= ' ' && c <= 126 ? c : '?'); report_state(symbol, s, expected_state_str); BFREE(s); } @@ -464,4 +454,30 @@ static void report_state(char *symbol, char *text, char *expected_state_str) } + +static int change_string_delimiters(pass_to_flex *extra, char *delim_expr) +{ + if (strlen(delim_expr) != 2) { + yyerror(NULL, "invalid string delimiter specification -- %s", delim_expr); + return 1; + } + if (delim_expr[0] == delim_expr[1]) { + yyerror(NULL, "string delimiter and escape char may not be the same"); + return 1; + } + if (strchr (LEX_SDELIM, delim_expr[1]) == NULL) { + yyerror(NULL, "invalid string delimiter -- %c (try one of %s)", delim_expr[1], LEX_SDELIM_RECOMMENDED); + return 1; + } + + #ifdef LEXER_DEBUG + fprintf(stderr, "YDELIMS: change_string_delimiters('%c', '%c')\n", delim_expr[0], delim_expr[1]); + #endif + extra->sesc = delim_expr[0]; + extra->sdel = delim_expr[1]; + + return 0; +} + + /*EOF*/ /* vim: set cindent sw=4: */ diff --git a/src/parsecode.c b/src/parsecode.c index 3012e5f..2a3e763 100644 --- a/src/parsecode.c +++ b/src/parsecode.c @@ -39,48 +39,11 @@ #include "lex.yy.h" -/* - * Valid characters to be used as string delimiters. Note that the - * following list must correspond to the DELIM definition in lexer.l. - */ -#define LEX_SDELIM "\"~'`!@%&*=:;<>?/|.\\" static pcre2_code *eol_pattern = NULL; -static void chg_strdelims (pass_to_bison *bison_args, const char asesc, const char asdel) -{ - #ifdef PARSER_DEBUG - fprintf (stderr, " STATUS: chg_strdelims ('%c', '%c') - This changes lexer behavior!\n", asesc, asdel); - #endif - bison_args->sesc = asesc; - bison_args->sdel = asdel; -} - - - -int action_chg_delim(pass_to_bison *bison_args, char *delim_expr) -{ - if (strlen(delim_expr) != 2) { - yyerror(bison_args, "invalid string delimiter specification -- %s", delim_expr); - return RC_ERROR; - } - if (delim_expr[0] == delim_expr[1]) { - yyerror(bison_args, "string delimiter and escape char may not be the same"); - return RC_ERROR; - } - if (strchr (LEX_SDELIM, delim_expr[1]) == NULL) { - yyerror(bison_args, "invalid string delimiter -- %c (try one of %s)", - delim_expr[1], LEX_SDELIM); - return RC_ERROR; - } - chg_strdelims(bison_args, delim_expr[0], delim_expr[1]); - return RC_SUCCESS; -} - - - static int check_sizes(pass_to_bison *bison_args) /* * For the author's convenience, it is required that shapes on one side @@ -95,7 +58,7 @@ static int check_sizes(pass_to_bison *bison_args) int i, j, k; #ifdef PARSER_DEBUG - fprintf (stderr, "check_sizes()\n"); + fprintf (stderr, " Parser: check_sizes()\n"); #endif for (i=0; inum_mandatory = 0; bison_args->time_for_se_check = 0; bison_args->num_shapespec = 0; - chg_strdelims(bison_args, '\\', '\"'); /* * Clear current design @@ -358,7 +320,7 @@ static int full_parse_required() result = opt.r || opt.l || (opt.query != NULL && !query_is_undoc()); } #ifdef DEBUG - fprintf(stderr, "full_parse_required() -> %s\n", result ? "true" : "false"); + fprintf(stderr, " Parser: full_parse_required() -> %s\n", result ? "true" : "false"); #endif return result; } @@ -642,9 +604,9 @@ int action_finalize_shapes(pass_to_bison *bison_args) } } #ifdef PARSER_DEBUG - fprintf (stderr, "Minimum box dimensions: width %d height %d\n", + fprintf (stderr, " Parser: Minimum box dimensions: width %d height %d\n", (int) curdes.minwidth, (int) curdes.minheight); - fprintf (stderr, "Maximum shape height: %d\n", + fprintf (stderr, " Parser: Maximum shape height: %d\n", (int) curdes.maxshapeheight); #endif return RC_SUCCESS; @@ -662,7 +624,6 @@ int action_finalize_shapes(pass_to_bison *bison_args) */ int action_start_parsing_design(pass_to_bison *bison_args, char *design_name) { - chg_strdelims(bison_args, '\\', '\"'); bison_args->speeding = 0; bison_args->skipping = 0; @@ -674,9 +635,10 @@ int action_start_parsing_design(pass_to_bison *bison_args, char *design_name) if (!design_needed(bison_args)) { bison_args->speeding = 1; - begin_speedmode(bison_args->lexer_state); - init_design(bison_args, &(curdes)); - return RC_ERROR; + #ifdef PARSER_DEBUG + fprintf (stderr, " Parser: Skipping to next design (lexer doesn't know!)\n"); + #endif + return RC_ERROR; /* trigger the parser's `error` rule, which will skip to the next design */ } return RC_SUCCESS; } @@ -693,7 +655,7 @@ int action_start_parsing_design(pass_to_bison *bison_args, char *design_name) int action_parent_config(pass_to_bison *bison_args, char *filepath) { #ifdef PARSER_DEBUG - fprintf (stderr, "parent config file specified: [%s]\n", filepath); + fprintf (stderr, " Parser: parent config file specified: [%s]\n", filepath); #endif if (filepath == NULL || filepath[0] == '\0') { bison_args->skipping = 1; @@ -720,13 +682,13 @@ int action_parent_config(pass_to_bison *bison_args, char *filepath) } } #ifdef PARSER_DEBUG - fprintf (stderr, "parent config file path resolved: [%s]\n", filepath); + fprintf (stderr, " Parser: parent config file path resolved: [%s]\n", filepath); #endif int is_new = !array_contains(bison_args->parent_configs, bison_args->num_parent_configs, filepath); if (!is_new) { #ifdef PARSER_DEBUG - fprintf (stderr, "duplicate parent / cycle: [%s]\n", filepath); + fprintf (stderr, " Parser: duplicate parent / cycle: [%s]\n", filepath); #endif } else { @@ -822,7 +784,7 @@ int action_add_design(pass_to_bison *bison_args, char *design_primary_name, char int action_record_keyword(pass_to_bison *bison_args, char *keyword, char *value) { #ifdef PARSER_DEBUG - fprintf (stderr, "entry rule fulfilled [%s = %s]\n", keyword, value); + fprintf (stderr, " Parser: entry rule fulfilled [%s = %s]\n", keyword, value); #endif if (strcasecmp (keyword, "author") == 0) { curdes.author = (char *) strdup (value); @@ -946,7 +908,7 @@ int action_add_alias(pass_to_bison *bison_args, char *alias_name) } if (alias_exists_in_child_configs(bison_args, alias_name)) { #ifdef PARSER_DEBUG - fprintf (stderr, "alias already used by child config, dropping: %s\n", alias_name); + fprintf (stderr, " Parser: alias already used by child config, dropping: %s\n", alias_name); #endif } else { @@ -978,7 +940,7 @@ static char *adjust_eols(char *sample) int action_sample_block(pass_to_bison *bison_args, char *sample) { #ifdef PARSER_DEBUG - fprintf(stderr, "SAMPLE block rule satisfied\n"); + fprintf(stderr, " Parser: SAMPLE block rule satisfied\n"); #endif if (curdes.sample) { diff --git a/src/parsecode.h b/src/parsecode.h index c8db753..121d288 100644 --- a/src/parsecode.h +++ b/src/parsecode.h @@ -48,9 +48,6 @@ int action_init_parser(pass_to_bison *bison_args); -int action_chg_delim(pass_to_bison *bison_args, char *delim_expr); - - /** * Rule action called when a shape list was fully parsed. * @param bison_args the parser state diff --git a/src/parser.y b/src/parser.y index 4950b2b..e895ba6 100644 --- a/src/parser.y +++ b/src/parser.y @@ -47,12 +47,6 @@ typedef struct { /** the path to the config file we are parsing */ char *config_file; - /** the currently active string delimiter character */ - char sdel; - - /** the currently active string escape character */ - char sesc; - int num_mandatory; int time_for_se_check; @@ -140,7 +134,7 @@ typedef struct { %token SHAPE %token YNUMBER %token YRXPFLAG -%token YDELWORD +%token YDELIMSPEC %type shape_def %type shape_lines @@ -203,7 +197,16 @@ config_file: config_file design_or_error | design_or_error | config_file parent_ design_or_error: design | error { - if (!(bison_args->speeding) && !(bison_args->skipping)) { + /* reset alias list, as those are collected even when speedmode is on */ + #ifdef PARSER_DEBUG + fprintf (stderr, " Parser: Discarding token [skipping=%s, speeding=%s]\n", + bison_args->skipping ? "true" : "false", bison_args->speeding ? "true" : "false"); + #endif + if (curdes.aliases[0] != NULL) { + BFREE(curdes.aliases); + curdes.aliases = (char **) calloc(1, sizeof(char *)); + } + if (!bison_args->speeding && !bison_args->skipping) { recover(bison_args); yyerror(bison_args, "skipping to next design"); bison_args->skipping = 1; @@ -260,14 +263,14 @@ entry: KEYWORD STRING } else { #ifdef PARSER_DEBUG - fprintf (stderr, "%s: Discarding entry [%s = %s].\n", PROJECT, "parent", filename); + fprintf (stderr, " Parser: Discarding entry [%s = %s].\n", "parent", filename); #endif } } -| YCHGDEL YDELWORD +| YCHGDEL YDELIMSPEC { - invoke_action(action_chg_delim(bison_args, $2)); + /* string delimiters were changed - this is a lexer thing. ignore here. */ } | YTAGS '(' tag_list ')' | YTAGS tag_entry @@ -275,7 +278,7 @@ entry: KEYWORD STRING | WORD STRING { #ifdef PARSER_DEBUG - fprintf (stderr, "%s: Discarding entry [%s = %s].\n", PROJECT, $1, $2); + fprintf (stderr, " Parser: Discarding entry [%s = %s].\n", $1, $2); #endif } ; @@ -492,4 +495,5 @@ wlist_entry: WORD YNUMBER %% + /*EOF*/ /* vim: set sw=4 cindent: */ diff --git a/src/parsing.c b/src/parsing.c index 50d271a..7680caf 100644 --- a/src/parsing.c +++ b/src/parsing.c @@ -135,8 +135,6 @@ static pass_to_bison new_bison_args(const char *config_file) bison_args.num_designs = 0; bison_args.design_idx = 0; bison_args.config_file = (char *) config_file; - bison_args.sdel = '\"'; /* sdel is shared by flex and bison */ - bison_args.sesc = '\\'; /* sesc is shared by flex and bison */ bison_args.num_mandatory = 0; bison_args.time_for_se_check = 0; bison_args.num_shapespec = 0; @@ -150,12 +148,12 @@ static pass_to_bison new_bison_args(const char *config_file) -static pass_to_flex new_flex_extra_data(pass_to_bison *bison_args) +static pass_to_flex new_flex_extra_data() { pass_to_flex flex_extra_data; flex_extra_data.yyerrcnt = 0; - flex_extra_data.sdel_ptr = &(bison_args->sdel); - flex_extra_data.sesc_ptr = &(bison_args->sesc); + flex_extra_data.sdel = '\"'; + flex_extra_data.sesc = '\\'; return flex_extra_data; } @@ -170,7 +168,7 @@ static pass_to_bison parse_config_file(const char *config_file, design_t *child_ pass_to_bison bison_args = new_bison_args(config_file); bison_args.child_configs = child_configs; bison_args.num_child_configs = num_child_configs; - pass_to_flex flex_extra_data = new_flex_extra_data(&bison_args); + pass_to_flex flex_extra_data = new_flex_extra_data(); current_bison_args = &bison_args; yylex_init (&(bison_args.lexer_state)); diff --git a/test/131_multiple_parent_configs_ref_after_broken.txt b/test/131_multiple_parent_configs_ref_after_broken.txt index c9e469c..e03d0c1 100644 --- a/test/131_multiple_parent_configs_ref_after_broken.txt +++ b/test/131_multiple_parent_configs_ref_after_broken.txt @@ -11,7 +11,7 @@ that speedmode is properly terminated.) :EXPECTED boxes: 131_data/B.cfg: line 32: entries SAMPLE, SHAPES, and ELASTIC are mandatory boxes: 131_data/B.cfg: line 32: skipping to next design -boxes: 131_data/A.cfg: line 29: string expected +boxes: 131_data/A.cfg: line 29: syntax error boxes: 131_data/A.cfg: line 29: skipping to next design 3 Available Styles: ------------------- diff --git a/test/137_design_alias_no_accumulation.txt b/test/137_design_alias_no_accumulation.txt index 18fe5b9..21f011c 100644 --- a/test/137_design_alias_no_accumulation.txt +++ b/test/137_design_alias_no_accumulation.txt @@ -1,5 +1,5 @@ :DESC -Test that the list of aliases is properly reset when lexer is in speedmode. +Test that the list of aliases is properly reset when parser is in speedmode. :ARGS -f 137_design_alias_no_accumulation.cfg -d alias3b -l diff --git a/test/168_parent_keyword_in_sample_ok.cfg b/test/168_parent_keyword_in_sample_ok.cfg new file mode 100644 index 0000000..6cc8129 --- /dev/null +++ b/test/168_parent_keyword_in_sample_ok.cfg @@ -0,0 +1,33 @@ + +BOX designA + +sample + A mentions the word parent THIS_IS_NOT_A_CONFIG_FILE +ends + +shapes { + w ("A") +} + +elastic ( + w +) + +END designA + + +BOX designB + +sample + B is the one we select +ends + +shapes { + w ("B") +} + +elastic ( + w +) + +END designB diff --git a/test/168_parent_keyword_in_sample_ok.txt b/test/168_parent_keyword_in_sample_ok.txt new file mode 100644 index 0000000..820de64 --- /dev/null +++ b/test/168_parent_keyword_in_sample_ok.txt @@ -0,0 +1,27 @@ +:DESC +Test that the keyword 'parent' may occur as a regular string in a sample block, without triggering parent config file +resolution. + +:ARGS +-f 168_parent_keyword_in_sample_ok.cfg -d designB -l +:INPUT +:OUTPUT-FILTER +:EXPECTED +Complete Design Information for "designB": +------------------------------------------ +Alias Names: none +Author: (unknown author) +Original Designer: (unknown artist) +Creation Date: (unknown) +Current Revision: (unknown) +Configuration File: 168_parent_keyword_in_sample_ok.cfg +Indentation Mode: box (indent box) +Replacement Rules: none +Reversion Rules: none +Minimum Box Dimensions: 3 x 3 (width x height) +Default Padding: none +Default Killblank: no +Tags: none +Elastic Shapes: N, E, S, W +Defined Shapes: W: "B" +:EOF