1: /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
   2: 
   3: /*
   4:   $Header: b2syn.c,v 1.4 85/08/22 16:56:25 timo Exp $
   5: */
   6: 
   7: #include "b.h"
   8: #include "b0con.h"
   9: #include "b1obj.h"
  10: #include "b2key.h"
  11: #include "b2syn.h"
  12: #include "b3scr.h"
  13: #include "b3env.h"
  14: #include "b3err.h"
  15: 
  16: #define TABSIZE 8 /* Number of spaces assumed for a tab on a file.
  17: 		     (Some editors insist on emitting tabs wherever
  18: 		     they can, and always assume 8 spaces for a tab.
  19: 		     Even when the editor can be instructed not to
  20: 		     do this, beginning users won't know about this,
  21: 		     so we'll always assume the default tab size.
  22: 		     Advanced users who used to instruct their editor
  23: 		     to set tab stops every 4 spaces will have to
  24: 		     unlearn this habit.  But that's the price for
  25: 		     over-cleverness :-)
  26: 		     The indent increment is still 4 spaces!
  27: 		     When the B interpreter outputs text, it never uses
  28: 		     tabs but always emits 4 spaces for each indent level.
  29: 		     Note that the B editor also has a #defined constant
  30: 		     which sets the number of spaces for a tab on a file.
  31: 		     Finally the B editor *displays* indents as 3 spaces,
  32: 		     but *writes* them to the file as 4, so a neat
  33: 		     lay-out on the screen may look a bit garbled
  34: 		     when the file is printed.  Sorry.  */
  35: 
  36: Visible txptr tx, ceol;
  37: 
  38: Visible Procedure skipsp(tx) txptr *tx; {
  39:     while(Space(Char(*tx))) (*tx)++;
  40: }
  41: 
  42: Visible bool keymark(c) char c; {
  43:     return Cap(c) || Dig(c) || c=='\'' || c=='"';
  44: }
  45: 
  46: Hidden bool tagmark(c) char c; {
  47:     return Letter(c) || Dig(c) || c=='\'' || c=='"';
  48: }
  49: 
  50: Hidden bool keytagmark(c) char c; {
  51:     return keymark(c) || Letter(c);
  52: }
  53: 
  54: Visible bool is_expr(c) char c; {
  55:     return Letter(c) || c == '(' || Dig(c) || c == '.' ||
  56:         c == '\'' || c == '"' || c == '{' ||
  57:         c=='~' ||
  58:         c=='*' || c=='/' || c=='+' || c=='-' || c=='#';
  59: }
  60: 
  61: /* ******************************************************************** */
  62: /*		cr_text							*/
  63: /* ******************************************************************** */
  64: 
  65: Visible value cr_text(p, q) txptr p, q; {
  66:     /* Messes with the input line, which is a bit nasty,
  67: 	   but considered preferable to copying to a separate buffer */
  68:     value t;
  69:     char save= Char(q);
  70:     Char(q)= '\0';
  71:     t= mk_text(p);
  72:     Char(q)= save;
  73:     return t;
  74: }
  75: 
  76: /* ******************************************************************** */
  77: /*		find, findceol, req, findrel				*/
  78: /* ******************************************************************** */
  79: 
  80: #define Ptr     (*ftx)
  81: #define Nokeymark   '+'
  82: 
  83: Hidden bool E_number(tx) txptr tx; {
  84:     return Char(tx) == 'E' && Dig(Char(tx+1)) &&
  85:         (Dig(Char(tx-1)) || Char(tx-1) == '.');
  86: }
  87: 
  88: Hidden bool search(find_kw, s, q, ftx, ttx)
  89:     bool find_kw; string s; txptr q, *ftx, *ttx; {
  90: 
  91:     intlet parcnt= 0; bool outs= Yes, kw= No; char aq, lc= Nokeymark;
  92:     while (Ptr < q) {
  93:         if (outs) {
  94:             if (parcnt == 0) {
  95:                 if (find_kw) {
  96:                     if (Cap(Char(Ptr)) && !E_number(Ptr))
  97:                         return Yes;
  98:                 } else if (Char(Ptr) == *s) {
  99:                     string t= s+1;
 100:                     *ttx= Ptr+1;
 101:                     while (*t && *ttx < q) {
 102:                         if (*t != Char(*ttx)) break;
 103:                         else { t++; (*ttx)++; }
 104:                     }
 105:                     if (*t);
 106:                     else if (Cap(*s) &&
 107:                          (kw || keymark(Char(*ttx))));
 108:                     else return Yes;
 109:                 }
 110:             }
 111:             switch (Char(Ptr)) {
 112:                 case '(': case '{': case '[':
 113:                     parcnt++; break;
 114:                 case ')': case '}': case ']':
 115:                     if (parcnt > 0) parcnt--; break;
 116:                 case '\'': case '"':
 117:                     if (!keytagmark(lc)) {
 118:                         outs= No; aq= Char(Ptr);
 119:                     }
 120:                     break;
 121:                 default:
 122:                     break;
 123:             }
 124:             lc= Char(Ptr); kw= kw ? keymark(lc) : Cap(lc);
 125:         } else {
 126:             if (Char(Ptr) == aq)
 127:                 { outs= Yes; kw= No; lc= Nokeymark; }
 128:             else if (Char(Ptr) == '`') {
 129:                 Ptr++;
 130:                 if (!search(No, "`", q, &Ptr, ttx)) return No;
 131:             }
 132:         }
 133:         Ptr++;
 134:     }
 135:     return No;
 136: }
 137: 
 138: /* ********************************************************************	*/
 139: 
 140: Visible bool find(s, q, ftx, ttx) string s; txptr q, *ftx, *ttx; {
 141:     return search(No, s, q, (*ftx= tx, ftx), ttx);
 142: }
 143: 
 144: Forward txptr lcol();
 145: 
 146: Visible Procedure findceol() {
 147:     txptr q= lcol(), ttx;
 148:     if (!find("\\", q, &ceol, &ttx)) ceol= q;
 149: }
 150: 
 151: Visible Procedure req(s, q, ftx, ttx) string s; txptr q, *ftx, *ttx; {
 152:     if (!find(s, q, ftx, ttx)) {
 153:         parerr2(MESS(2400, "cannot find expected "), MESSMAKE(s));
 154:         *ftx= tx; *ttx= q;
 155:     }
 156: }
 157: 
 158: Hidden bool relsearch(s, q, ftx) string s; txptr q, *ftx; {
 159:     txptr ttx;
 160:     Ptr= tx;
 161:     while (search(No, s, q, &Ptr, &ttx))
 162:         switch (Char(Ptr)) {
 163:             case '<': if (Char(Ptr+1) == '<') Ptr= ++ttx;
 164:                   else if (Char(Ptr-1) == '>') Ptr= ttx;
 165:                   else return Yes;
 166:                   break;
 167:             case '>': if (Char(Ptr+1) == '<') Ptr= ++ttx;
 168:                   else if (Char(Ptr+1) == '>') Ptr= ++ttx;
 169:                   else return Yes;
 170:                   break;
 171:             case '=': return Yes;
 172:             default:  return No;
 173:         }
 174:     return No;
 175: }
 176: 
 177: Visible bool findrel(q, ftx) txptr q, *ftx; {
 178:     txptr ttx;
 179:     Ptr= q;
 180:     if (relsearch("<", Ptr, &ttx)) Ptr= ttx;
 181:     if (relsearch(">", Ptr, &ttx)) Ptr= ttx;
 182:     if (relsearch("=", Ptr, &ttx)) Ptr= ttx;
 183:     return Ptr < q;
 184: }
 185: 
 186: /* ******************************************************************** */
 187: /*		tag, keyword, findkw					*/
 188: /* ******************************************************************** */
 189: 
 190: Visible bool is_tag(v) value *v; {
 191:     if (!Letter(Char(tx))) return No;
 192:     *v= tag();
 193:     return Yes;
 194: }
 195: 
 196: Visible value tag() {
 197:     txptr tx0= tx;
 198:     if (!Letter(Char(tx))) parerr(MESS(2401, "no tag where expected"));
 199:     while (tagmark(Char(tx))) tx++;
 200:     return cr_text(tx0, tx);
 201: }
 202: 
 203: Visible bool is_keyword(v) value *v; {
 204:     if (!Cap(Char(tx))) return No;
 205:     *v= keyword();
 206:     return Yes;
 207: }
 208: 
 209: Visible value keyword() {
 210:     txptr tx0= tx;
 211:     if (!Cap(Char(tx))) parerr(MESS(2402, "no keyword where expected"));
 212:     while (keymark(Char(tx))) tx++;
 213:     return cr_text(tx0, tx);
 214: }
 215: 
 216: Visible bool findkw(q, ftx) txptr q, *ftx; {
 217:     txptr ttx;
 218:     Ptr= tx;
 219:     return search(Yes, "", q, &Ptr, &ttx);
 220: }
 221: 
 222: /* ******************************************************************** */
 223: /*		upto, nothing, ateol, atkw, need			*/
 224: /* ******************************************************************** */
 225: 
 226: Visible Procedure upto(q, s) txptr q; string s; {
 227:     skipsp(&tx);
 228:     if (Text(q)) {
 229:         parerr2(MESS(2403, "something unexpected following "),
 230:             MESSMAKE(s));
 231:         tx= q;
 232:     }
 233: }
 234: 
 235: Visible bool nothing(q, s) txptr q; string s; {
 236:     if (!Text(q)) {
 237:         if (Char(tx-1) == ' ') tx--;
 238:         parerr2(MESS(2404, "nothing instead of expected "),
 239:             MESSMAKE(s));
 240:         return Yes;
 241:     }
 242:     return No;
 243: }
 244: 
 245: Hidden bool looked_ahead= No;
 246: Visible intlet cur_ilev;
 247: 
 248: Visible bool ateol() {
 249:     if (looked_ahead) return Yes;
 250:     skipsp(&tx);
 251:     return Eol(tx);
 252: }
 253: 
 254: Visible bool atkw(s) string s; {
 255:     txptr tx0= tx;
 256:     while (*s) if (*s++ != Char(tx0++)) return No;
 257:     if (keymark(Char(tx0))) return No;
 258:     tx= tx0;
 259:     return Yes;
 260: }
 261: 
 262: Visible Procedure need(s) string s; {
 263:     string t= s;
 264:     skipsp(&tx);
 265:     while (*t)
 266:         if (*t++ != Char(tx++)) {
 267:             tx--;
 268:             parerr2(MESS(2405, "according to the syntax I expected "),
 269:                 MESSMAKE(s));
 270:             return;
 271:         }
 272: }
 273: 
 274: /* ******************************************************************** */
 275: /*		buffer handling						*/
 276: /* ******************************************************************** */
 277: 
 278: Visible txptr first_col;
 279: 
 280: Visible txptr fcol() { /* the first position of the current line */
 281:     return first_col;
 282: }
 283: 
 284: Hidden txptr lcol() { /* the position beyond the last character of the line */
 285:     txptr ax= tx;
 286:     while (!Eol(ax)) ax++;
 287:     return ax;
 288: }
 289: 
 290: Visible intlet ilev() {
 291:     intlet i;
 292:     if (looked_ahead) {
 293:         looked_ahead= No;
 294:         return cur_ilev;
 295:     } else {
 296:         first_col= tx= getline();
 297:         looked_ahead= No;
 298:         lino++;
 299:         f_lino++;
 300:         i= 0;
 301:         while (Space(Char(tx)))
 302:             if (Char(tx++) == ' ') i++;
 303:             else i= (i/TABSIZE+1)*TABSIZE;
 304:         if (Char(tx) == '\\') return cur_ilev= 0;
 305:         if (Char(tx) == '\n') return cur_ilev= 0;
 306:         if (i%4 == 2)
 307:             parerr(MESS(2406, "cannot make out indentation; use tab to indent"));
 308:         return cur_ilev= (i+1)/4; /* small deviation accepted */
 309:     }
 310: }
 311: 
 312: Visible Procedure veli() { /* After a look-ahead call of ilev */
 313:     looked_ahead= Yes;
 314: }
 315: 
 316: Visible Procedure first_ilev() { /* initialise read buffer for new input */
 317:     looked_ahead= No;
 318:     VOID ilev();
 319:     findceol();
 320: }
 321: 
 322: /* ********************************************************************	*/
 323: 
 324: /* The reserved keywords that a user command may not begin with: */
 325: 
 326: Visible value kwlist;
 327: 
 328: Hidden string kwtab[] = {
 329:     K_SHARE, K_CHECK, K_CHOOSE, K_DELETE, K_DRAW, K_FAIL, K_FOR,
 330:     K_HOW_TO, K_IF, K_INSERT, K_PUT, K_QUIT, K_READ, K_REMOVE,
 331:     K_REPORT, K_RETURN, K_SELECT, K_SET_RANDOM, K_SUCCEED,
 332:     K_TEST, K_WHILE, K_WRITE, K_YIELD, K_ELSE,
 333:     ""
 334: };
 335: 
 336: Visible Procedure initsyn() {
 337:     value v;
 338:     string *kw;
 339:     kwlist= mk_elt();
 340:     for (kw= kwtab; **kw != '\0'; kw++) {
 341:         insert(v= mk_text(*kw), &kwlist);
 342:         release(v);
 343:     }
 344: }
 345: 
 346: Visible Procedure endsyn() {
 347:     release(kwlist); kwlist= Vnil;
 348: }
 349: 
 350: /* ******************************************************************** */
 351: /*		signs							*/
 352: /* ********************************************************************	*/
 353: 
 354: Visible string textsign;
 355: 
 356: Hidden bool la_denum(tx0) txptr tx0; {
 357:     char l, r;
 358:     switch (l= Char(++tx0)) {
 359:         case '/':   r= '*'; break;
 360:         case '*':   r= '/'; break;
 361:         default:    return Yes;
 362:     }
 363:     do if (Char(++tx0) != r) return No; while (Char(++tx0) == l);
 364:     return Yes;
 365: }
 366: 
 367: #ifdef NOT_USED
 368: Visible bool colon_sign() {
 369:     return Char(tx) == ':' ? (tx++, Yes) : No;
 370: }
 371: #endif
 372: 
 373: Visible bool comment_sign() {
 374:     return Char(tx) == '\\' ? (tx++, Yes) : No;
 375: }
 376: 
 377: Visible bool nwl_sign() {
 378:     return Char(tx) == '/' && !la_denum(tx-1) ? (tx++, Yes) : No;
 379: }
 380: 
 381: Visible bool open_sign() {
 382:     return Char(tx) == '(' ? (tx++, Yes) : No;
 383: }
 384: 
 385: #ifdef NOT_USED
 386: Visible bool close_sign() {
 387:     return Char(tx) == ')' ? (tx++, Yes) : No;
 388: }
 389: #endif
 390: 
 391: #ifdef NOT_USED
 392: Visible bool comma_sign() {
 393:     return Char(tx) == ',' ? (tx++, Yes) : No;
 394: }
 395: #endif
 396: 
 397: Visible bool point_sign() {
 398:     return Char(tx) == '.' ? (tx++, Yes) : No;
 399: }
 400: 
 401: Visible bool apostrophe_sign() {
 402:     return Char(tx) == '\'' ? (tx++, textsign= "'", Yes) : No;
 403: }
 404: 
 405: Visible bool quote_sign() {
 406:     return Char(tx) == '"' ? (tx++, textsign= "\"", Yes) : No;
 407: }
 408: 
 409: Visible bool conv_sign() {
 410:     return Char(tx) == '`' ? (tx++, Yes) : No;
 411: }
 412: 
 413: Visible bool curlyopen_sign() {
 414:     return Char(tx) == '{' ? (tx++, Yes) : No;
 415: }
 416: 
 417: Visible bool curlyclose_sign() {
 418:     return Char(tx) == '}' ? (tx++, Yes) : No;
 419: }
 420: 
 421: Visible bool sub_sign() {
 422:     return Char(tx) == '[' ? (tx++, Yes) : No;
 423: }
 424: 
 425: #ifdef NOT_USED
 426: Visible bool bus_sign() {
 427:     return Char(tx) == ']' ? (tx++, Yes) : No;
 428: }
 429: #endif
 430: 
 431: Visible bool behead_sign() {
 432:     return Char(tx) == '@' ? (tx++, textsign= "@", Yes) : No;
 433: }
 434: 
 435: Visible bool curtl_sign() {
 436:     return Char(tx) == '|' ? (tx++, textsign= "|", Yes) : No;
 437: }
 438: 
 439: Visible bool about_sign() {
 440:     return Char(tx) == '~' ? (tx++, textsign= "~", Yes) : No;
 441: }
 442: 
 443: Visible bool plus_sign() {
 444:     return Char(tx) == '+' ? (tx++, textsign= "+", Yes) : No;
 445: }
 446: 
 447: Visible bool minus_sign() {
 448:     return Char(tx) == '-' ? (tx++, textsign= "-", Yes) : No;
 449: }
 450: 
 451: Visible bool times_sign() {
 452:     return Char(tx) == '*' && la_denum(tx)
 453:         ? (tx++, textsign= "*", Yes) : No;
 454: }
 455: 
 456: Visible bool over_sign() {
 457:     return Char(tx) == '/' && la_denum(tx)
 458:         ? (tx++, textsign= "/", Yes) : No;
 459: }
 460: 
 461: Visible bool power_sign() {
 462:     return Char(tx) == '*' && Char(tx+1) == '*' && la_denum(tx+1)
 463:         ? (tx+= 2, textsign= "**", Yes) : No;
 464: }
 465: 
 466: Visible bool numtor_sign() {
 467:     return Char(tx) == '*' && Char(tx+1) == '/' && la_denum(tx+1)
 468:         ? (tx+= 2, textsign= "*/", Yes) : No;
 469: }
 470: 
 471: Visible bool denomtor_sign() {
 472:     return Char(tx) == '/' && Char(tx+1) == '*' && la_denum(tx+1)
 473:         ? (tx+= 2, textsign= "/*", Yes) : No;
 474: }
 475: 
 476: Visible bool join_sign() {
 477:     return Char(tx) == '^' && Char(tx+1) != '^'
 478:         ? (tx++, textsign= "^", Yes) : No;
 479: }
 480: 
 481: Visible bool reptext_sign() {
 482:     return Char(tx) == '^' && Char(tx+1) == '^'
 483:         ? (tx+= 2, textsign= "^^", Yes) : No;
 484: }
 485: 
 486: Visible bool leftadj_sign() {
 487:     return Char(tx) == '<' && Char(tx+1) == '<'
 488:         ? (tx+= 2, textsign= "<<", Yes) : No;
 489: }
 490: 
 491: Visible bool center_sign() {
 492:     return Char(tx) == '>' && Char(tx+1) == '<'
 493:         ? (tx+= 2, textsign= "><", Yes) : No;
 494: }
 495: 
 496: Visible bool rightadj_sign() {
 497:     return Char(tx) == '>' && Char(tx+1) == '>'
 498:         ? (tx+= 2, textsign= ">>", Yes) : No;
 499: }
 500: 
 501: Visible bool number_sign() {
 502:     return Char(tx) == '#' ? (tx++, textsign= "#", Yes) : No;
 503: }
 504: 
 505: Visible bool less_than_sign() {
 506:     return Char(tx) == '<' && Char(tx+1) != '=' &&
 507:         Char(tx+1) != '>' && Char(tx+1) != '<'
 508:         ? (tx++, Yes) : No;
 509: }
 510: 
 511: Visible bool at_most_sign() {
 512:     return Char(tx) == '<' && Char(tx+1) == '=' ? (tx+= 2, Yes) : No;
 513: }
 514: 
 515: Visible bool equals_sign() {
 516:     return Char(tx) == '=' ? (tx++, Yes) : No;
 517: }
 518: 
 519: Visible bool unequal_sign() {
 520:     return Char(tx) == '<' && Char(tx+1) == '>' ? (tx+= 2, Yes) : No;
 521: }
 522: 
 523: Visible bool at_least_sign() {
 524:     return Char(tx) == '>' && Char(tx+1) == '=' ? (tx+= 2, Yes) : No;
 525: }
 526: 
 527: Visible bool greater_than_sign() {
 528:     return Char(tx) == '>' && Char(tx+1) != '='
 529:         && Char(tx+1) != '>' && Char(tx+1) != '<'
 530:         ? (tx++, Yes) : No;
 531: }
 532: 
 533: Visible bool dyamon_sign() {
 534:     return plus_sign() || minus_sign() || number_sign();
 535: }
 536: 
 537: Visible bool dya_sign() {
 538:     return times_sign() || over_sign() || power_sign() ||
 539:         join_sign() || reptext_sign() ||
 540:         leftadj_sign() || center_sign() || rightadj_sign();
 541: }
 542: 
 543: Visible bool mon_sign() {
 544:     return about_sign() || numtor_sign() || denomtor_sign();
 545: }
 546: 
 547: Visible bool trim_sign() {
 548:     return behead_sign() || curtl_sign();
 549: }
 550: 
 551: Visible bool check_keyword()        { return atkw(K_CHECK); }
 552: Visible bool choose_keyword()       { return atkw(K_CHOOSE); }
 553: Visible bool delete_keyword()       { return atkw(K_DELETE); }
 554: Visible bool draw_keyword()         { return atkw(K_DRAW); }
 555: Visible bool insert_keyword()       { return atkw(K_INSERT); }
 556: Visible bool put_keyword()      { return atkw(K_PUT); }
 557: Visible bool read_keyword()         { return atkw(K_READ); }
 558: Visible bool remove_keyword()       { return atkw(K_REMOVE); }
 559: Visible bool setrandom_keyword()    { return atkw(K_SET_RANDOM); }
 560: Visible bool write_keyword()        { return atkw(K_WRITE); }
 561: Visible bool fail_keyword()     { return atkw(K_FAIL); }
 562: Visible bool quit_keyword()         { return atkw(K_QUIT); }
 563: Visible bool return_keyword()       { return atkw(K_RETURN); }
 564: Visible bool report_keyword()       { return atkw(K_REPORT); }
 565: Visible bool succeed_keyword()      { return atkw(K_SUCCEED); }
 566: Visible bool if_keyword()       { return atkw(K_IF); }
 567: Visible bool select_keyword()       { return atkw(K_SELECT); }
 568: Visible bool while_keyword()        { return atkw(K_WHILE); }
 569: Visible bool for_keyword()      { return atkw(K_FOR); }
 570: Visible bool else_keyword()         { return atkw(K_ELSE); }
 571: #ifdef NOT_USED
 572: Visible bool and_keyword()      { return atkw(K_AND); }
 573: Visible bool or_keyword()       { return atkw(K_OR); }
 574: #endif
 575: Visible bool not_keyword()      { return atkw(K_NOT); }
 576: Visible bool some_keyword()         { return atkw(K_SOME); }
 577: Visible bool each_keyword()         { return atkw(K_EACH); }
 578: Visible bool no_keyword()       { return atkw(K_NO); }
 579: Visible bool how_to_keyword()       { return atkw(K_HOW_TO); }
 580: Visible bool yield_keyword()        { return atkw(K_YIELD); }
 581: Visible bool test_keyword()         { return atkw(K_TEST); }
 582: Visible bool share_keyword()        { return atkw(K_SHARE); }

Defined functions

E_number defined in line 83; used 1 times
  • in line 96
about_sign defined in line 439; used 2 times
and_keyword defined in line 572; used 1 times
atkw defined in line 254; used 30 times
behead_sign defined in line 431; used 2 times
bus_sign defined in line 426; used 1 times
close_sign defined in line 386; used 1 times
colon_sign defined in line 368; used 1 times
comma_sign defined in line 392; used 1 times
curtl_sign defined in line 435; used 2 times
denomtor_sign defined in line 471; used 2 times
endsyn defined in line 346; used 1 times
first_ilev defined in line 316; used 2 times
initsyn defined in line 336; used 1 times
keymark defined in line 42; used 7 times
keytagmark defined in line 50; used 1 times
la_denum defined in line 356; used 6 times
lcol defined in line 284; used 2 times
numtor_sign defined in line 466; used 2 times
or_keyword defined in line 573; used 1 times
relsearch defined in line 158; used 3 times
search defined in line 88; used 4 times
tag defined in line 196; used 2 times
tagmark defined in line 46; used 1 times

Defined variables

Hidden defined in line 284; never used
Procedure defined in line 316; never used
Visible defined in line 582; never used
ceol defined in line 36; used 2 times
  • in line 148(2)
cur_ilev defined in line 246; used 4 times
first_col defined in line 278; used 2 times
kwlist defined in line 326; used 4 times
kwtab defined in line 328; used 1 times
looked_ahead defined in line 245; used 6 times
textsign defined in line 354; used 18 times
tx defined in line 36; used 138 times

Defined macros

Nokeymark defined in line 81; used 2 times
Ptr defined in line 80; used 34 times
TABSIZE defined in line 16; used 2 times
  • in line 303(2)
Last modified: 1985-08-27
Generated: 2016-12-26
Generated by src2html V0.67
page hit count: 3052
Valid CSS Valid XHTML 1.0 Strict