1 /*
   2  * Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.javatest.regtest.config;
  27 
  28 import static com.sun.javatest.regtest.config.Expr.Token.*;
  29 
  30 /**
  31  * Class to support simple expressions for @requires.
  32  */
  33 public abstract class Expr {
  34 
  35     public static class Fault extends Exception {
  36         private static final long serialVersionUID = 1L;
  37         Fault(String msg) {
  38             super(msg);
  39         }
  40     };
  41 
  42     public interface Context {
  43         boolean isValidName(String name);
  44         String get(String name) throws Fault;
  45     }
  46 
  47     public static Expr parse(String s, Context c) throws Fault {
  48         Parser p = new Parser(s, c);
  49         return p.parse();
  50     }
  51 
  52     public abstract String eval(Context c) throws Fault;
  53 
  54     public boolean evalBoolean(Context c) throws Fault {
  55         String s = eval(c);
  56         if (s.equals("true")) {
  57             return true;
  58         } else if (s.equals("false")) {
  59             return false;
  60         } else {
  61             throw new Fault("invalid boolean value: `" + s + "' for expression `" + this + "'");
  62         }
  63     }
  64 
  65     public long evalNumber(Context c) throws Fault {
  66         String s = eval(c);
  67         try {
  68             return Long.parseLong(s);
  69         } catch (NumberFormatException ex) {
  70             throw new Fault("invalid numeric value: " + s);
  71         }
  72     }
  73 
  74     abstract int precedence();
  75 
  76     Expr order() {
  77         return this;
  78     }
  79 
  80     static class Parser {
  81 
  82         Parser(String text, Context context) throws Fault {
  83             this.context = context;
  84             this.text = text;
  85             nextToken();
  86         }
  87 
  88         Expr parse() throws Fault {
  89             Expr e = parseExpr();
  90             expect(END);
  91             return e;
  92         }
  93 
  94         Expr parseExpr() throws Fault {
  95             for (Expr e = parseTerm(); e != null; e = e.order()) {
  96                 switch (token) {
  97                     case ADD:
  98                         nextToken();
  99                         e = new AddExpr(e, parseTerm());
 100                         break;
 101                     case AND:
 102                         nextToken();
 103                         e = new AndExpr(e, parseTerm());
 104                         break;
 105                     case DIV:
 106                         nextToken();
 107                         e = new DivideExpr(e, parseTerm());
 108                         break;
 109                     case EQ:
 110                         nextToken();
 111                         e = new EqualExpr(e, parseTerm());
 112                         break;
 113                     case GE:
 114                         nextToken();
 115                         e = new GreaterEqualExpr(e, parseTerm());
 116                         break;
 117                     case GT:
 118                         nextToken();
 119                         e = new GreaterExpr(e, parseTerm());
 120                         break;
 121                     case LE:
 122                         nextToken();
 123                         e = new LessEqualExpr(e, parseTerm());
 124                         break;
 125                     case LT:
 126                         nextToken();
 127                         e = new LessExpr(e, parseTerm());
 128                         break;
 129                     case MATCH:
 130                         nextToken();
 131                         e = new MatchExpr(e, parseTerm());
 132                         break;
 133                     case MUL:
 134                         nextToken();
 135                         e = new MultiplyExpr(e, parseTerm());
 136                         break;
 137                     case NE:
 138                         nextToken();
 139                         e = new NotEqualExpr(e, parseTerm());
 140                         break;
 141                     case OR:
 142                         nextToken();
 143                         e = new OrExpr(e, parseTerm());
 144                         break;
 145                     case REM:
 146                         nextToken();
 147                         e = new RemainderExpr(e, parseTerm());
 148                         break;
 149                     case SUB:
 150                         nextToken();
 151                         e = new SubtractExpr(e, parseTerm());
 152                         break;
 153                     default:
 154                         return e;
 155                 }
 156             }
 157             // bogus return to keep compiler happy
 158             return null;
 159         }
 160 
 161         Expr parseTerm() throws Fault {
 162             switch (token) {
 163                 case NAME:
 164                     String id = idValue;
 165                     nextToken();
 166                     if (context.isValidName(id))
 167                         return new NameExpr(id);
 168                     else
 169                         throw new Fault("invalid name: " + id);
 170 
 171                 case NOT:
 172                     nextToken();
 173                     return new NotExpr(parseTerm());
 174 
 175                 case NUMBER:
 176                 case TRUE:
 177                 case FALSE:
 178                     String num = idValue;
 179                     nextToken();
 180                     return new NumberExpr(num);
 181 
 182                 case LPAREN:
 183                     nextToken();
 184                     Expr e = parseExpr();
 185                     expect(RPAREN);
 186                     return new ParenExpr(e);
 187 
 188                 case STRING:
 189                     String s = idValue;
 190                     nextToken();
 191                     return new StringExpr(s);
 192 
 193                 default:
 194                     throw new Fault(token.getText() + " not expected");
 195             }
 196         }
 197 
 198         private void expect(Token t) throws Fault {
 199             if (t == token) {
 200                 nextToken();
 201             } else {
 202                 throw new Fault(t.getText() + "expected, but " + token.getText() + " found");
 203             }
 204         }
 205 
 206         private void nextToken() throws Fault {
 207             while (index < text.length()) {
 208                 char c = text.charAt(index++);
 209                 switch (c) {
 210                     case ' ':
 211                     case '\t':
 212                         continue;
 213 
 214                     case '&':
 215                         token = AND;
 216                         return;
 217 
 218                     case '|':
 219                         token = OR;
 220                         return;
 221 
 222                     case '+':
 223                         token = ADD;
 224                         return;
 225 
 226                     case '-':
 227                         token = SUB;
 228                         return;
 229 
 230                     case '*':
 231                         token = MUL;
 232                         return;
 233 
 234                     case '/':
 235                         token = DIV;
 236                         return;
 237 
 238                     case '%':
 239                         token = REM;
 240                         return;
 241 
 242                     case '(':
 243                         token = LPAREN;
 244                         return;
 245 
 246                     case ')':
 247                         token = RPAREN;
 248                         return;
 249 
 250                     case '<':
 251                         if (index < text.length() && text.charAt(index) == '=') {
 252                             token = LE;
 253                             index++;
 254                         } else {
 255                             token = LT;
 256                         }
 257                         return;
 258 
 259                     case '>':
 260                         if (index < text.length() && text.charAt(index) == '=') {
 261                             token = GE;
 262                             index++;
 263                         } else {
 264                             token = GT;
 265                         }
 266                         return;
 267 
 268                     case '~':
 269                         if (index < text.length() && text.charAt(index) == '=') {
 270                             token = MATCH;
 271                             index++;
 272                         } else {
 273                             throw new Fault("unexpected character after `~'");
 274                         }
 275                         return;
 276 
 277                     case '=':
 278                         if (index < text.length() && text.charAt(index) == '=') {
 279                             token = EQ;
 280                             index++;
 281                         } else {
 282                             throw new Fault("unexpected character after `='");
 283                         }
 284                         return;
 285 
 286                     case '!':
 287                         if (index < text.length() && text.charAt(index) == '=') {
 288                             token = NE;
 289                             index++;
 290                         } else {
 291                             token = NOT;
 292                         }
 293                         return;
 294 
 295                     case '\"':
 296                         StringBuffer sb = new StringBuffer();
 297                         if (index < text.length()) {
 298                             c = text.charAt(index);
 299                             index++;
 300                         } else {
 301                             throw new Fault("invalid string constant");
 302                         }
 303                         while (c != '\"') {
 304                             if (c == '\\') {
 305                                 if (index < text.length()) {
 306                                     c = text.charAt(index);
 307                                     index++;
 308                                 } else {
 309                                     throw new Fault("invalid string constant");
 310                                 }
 311                                 switch (c) {
 312                                     // standard Java escapes; no Unicode (for now)
 313                                     case 'b':  c = '\b'; break;
 314                                     case 'f':  c = '\f'; break;
 315                                     case 'n':  c = '\n'; break;
 316                                     case 'r':  c = '\r'; break;
 317                                     case 't':  c = '\t'; break;
 318                                     case '\\': c = '\\'; break;
 319                                     case '\'': c = '\''; break;
 320                                     case '\"': c = '\"'; break;
 321                                     default:
 322                                         throw new Fault("invalid string constant");
 323                                 }
 324                             }
 325                             sb.append(c);
 326                             if (index < text.length()) {
 327                                 c = text.charAt(index);
 328                                 index++;
 329                             } else {
 330                                 break;
 331                             }
 332                         }
 333                         if (c == '\"') {
 334                             token = STRING;
 335                             idValue = String.valueOf(sb);
 336                         } else {
 337                             throw new Fault("invalid string constant");
 338                         }
 339                         return;
 340 
 341                     default:
 342                         if (Character.isUnicodeIdentifierStart(c)) {
 343                             idValue = String.valueOf(c);
 344                             while (index < text.length()) {
 345                                 c = text.charAt(index);
 346                                 if (Character.isUnicodeIdentifierPart(c) || c == '.') {
 347                                     if (!Character.isIdentifierIgnorable(c)) {
 348                                         idValue += c;
 349                                     }
 350                                     index++;
 351                                 } else {
 352                                     break;
 353                                 }
 354                             }
 355                             if (idValue.equalsIgnoreCase("true")) {
 356                                 token = TRUE;
 357                             } else if (idValue.equalsIgnoreCase("false")) {
 358                                 token = FALSE;
 359                             } else {
 360                                 token = NAME;
 361                             }
 362                             return;
 363                         } else if (Character.isDigit(c)) {
 364                             int start = index - 1;
 365                             while (index < text.length()) {
 366                                 c = text.charAt(index);
 367                                 if (Character.isDigit(c)) {
 368                                     index++;
 369                                 } else {
 370                                     switch (c) {
 371                                         case 'k': case 'K':
 372                                         case 'm': case 'M':
 373                                         case 'g': case 'G':
 374                                             index++;
 375                                     }
 376                                     break;
 377                                 }
 378                             }
 379                             token = NUMBER;
 380                             idValue = text.substring(start, index);
 381                             return;
 382                         } else {
 383                             throw new Fault("unrecognized character: `" + c + "'");
 384                         }
 385                 }
 386             }
 387             token = END;
 388         }
 389 
 390         private final Context context;
 391         private final String text;
 392         private int index;
 393         private Token token;
 394         private String idValue;
 395     }
 396 
 397     static enum Token {
 398         ADD("+"),
 399         AND("&"),
 400         DIV("/"),
 401         END("<end-of-expression>"),
 402         ERROR("<error>"),
 403         EQ("="),
 404         FALSE("false"),
 405         GE(">="),
 406         GT(">"),
 407         LE("<="),
 408         LPAREN("("),
 409         LT("<"),
 410         MATCH("~="),
 411         MUL("*"),
 412         NAME("<name>"),
 413         NE("!="),
 414         NOT("!"),
 415         NUMBER("<number>"),
 416         OR("|"),
 417         REM("%"),
 418         RPAREN(")"),
 419         STRING("<string>"),
 420         SUB("-"),
 421         TRUE("true");
 422         Token(String text) {
 423             this.text = text;
 424         }
 425         String getText() {
 426             return text.startsWith("<") ? text : "'" + text + "'";
 427         }
 428         final String text;
 429     };
 430 
 431     protected static final int PREC_LIT = 6;
 432     protected static final int PREC_NOT = 6;
 433     protected static final int PREC_NUM = 6;
 434     protected static final int PREC_PRN = 6;
 435     protected static final int PREC_TRM = 6;
 436     protected static final int PREC_DIV = 5;
 437     protected static final int PREC_MUL = 5;
 438     protected static final int PREC_REM = 5;
 439     protected static final int PREC_ADD = 4;
 440     protected static final int PREC_SUB = 4;
 441     protected static final int PREC_GE = 3;
 442     protected static final int PREC_GT = 3;
 443     protected static final int PREC_LE = 3;
 444     protected static final int PREC_LT = 3;
 445     protected static final int PREC_EQ = 2;
 446     protected static final int PREC_NE = 2;
 447     protected static final int PREC_AND = 1;
 448     protected static final int PREC_OR = 0;
 449 
 450     //--------------------------------------------------------------------------
 451 
 452     abstract static class BinaryExpr extends Expr {
 453 
 454         BinaryExpr(Expr left, Expr right) {
 455             this.left = left;
 456             this.right = right;
 457         }
 458 
 459         @Override
 460         Expr order() {
 461             if (precedence() > left.precedence() && left instanceof BinaryExpr) {
 462                 BinaryExpr e = (BinaryExpr) left;
 463                 left = e.right;
 464                 e.right = order();
 465                 return e;
 466             } else {
 467                 return this;
 468             }
 469         }
 470         protected Expr left;
 471         protected Expr right;
 472     }
 473 
 474     //--------------------------------------------------------------------------
 475 
 476     static class AddExpr extends BinaryExpr {
 477 
 478         AddExpr(Expr left, Expr right) {
 479             super(left, right);
 480         }
 481 
 482         public String eval(Context c) throws Fault {
 483             return String.valueOf(left.evalNumber(c) + right.evalNumber(c));
 484         }
 485 
 486         int precedence() {
 487             return PREC_ADD;
 488         }
 489 
 490         @Override
 491         public String toString() {
 492             return "`" + left + "+" + right + "'";
 493         }
 494     }
 495 
 496     //--------------------------------------------------------------------------
 497 
 498     static class AndExpr extends BinaryExpr {
 499 
 500         AndExpr(Expr left, Expr right) {
 501             super(left, right);
 502         }
 503 
 504         public String eval(Context c) throws Fault {
 505             return String.valueOf(left.evalBoolean(c) & right.evalBoolean(c));
 506         }
 507 
 508         int precedence() {
 509             return PREC_AND;
 510         }
 511 
 512         @Override
 513         public String toString() {
 514             return "`" + left + "&" + right + "'";
 515         }
 516     }
 517 
 518     //--------------------------------------------------------------------------
 519 
 520     static class DivideExpr extends BinaryExpr {
 521 
 522         DivideExpr(Expr left, Expr right) {
 523             super(left, right);
 524         }
 525 
 526         public String eval(Context c) throws Fault {
 527             return String.valueOf(left.evalNumber(c) / right.evalNumber(c));
 528         }
 529 
 530         int precedence() {
 531             return PREC_DIV;
 532         }
 533 
 534         @Override
 535         public String toString() {
 536             return "`" + left + "/" + right + "'";
 537         }
 538     }
 539 
 540     //--------------------------------------------------------------------------
 541 
 542     static class EqualExpr extends BinaryExpr {
 543 
 544         EqualExpr(Expr left, Expr right) {
 545             super(left, right);
 546         }
 547 
 548         public String eval(Context c) throws Fault {
 549             return String.valueOf(left.eval(c).equalsIgnoreCase(right.eval(c)));
 550         }
 551 
 552         int precedence() {
 553             return PREC_EQ;
 554         }
 555 
 556         @Override
 557         public String toString() {
 558             return "`" + left + "==" + right + "'";
 559         }
 560     }
 561 
 562     //--------------------------------------------------------------------------
 563 
 564     static class GreaterExpr extends BinaryExpr {
 565 
 566         GreaterExpr(Expr left, Expr right) {
 567             super(left, right);
 568         }
 569 
 570         public String eval(Context c) throws Fault {
 571             return String.valueOf(left.evalNumber(c) > right.evalNumber(c));
 572         }
 573 
 574         int precedence() {
 575             return PREC_GT;
 576         }
 577 
 578         @Override
 579         public String toString() {
 580             return "`" + left + ">" + right + "'";
 581         }
 582     }
 583 
 584     //--------------------------------------------------------------------------
 585 
 586     static class GreaterEqualExpr extends BinaryExpr {
 587 
 588         GreaterEqualExpr(Expr left, Expr right) {
 589             super(left, right);
 590         }
 591 
 592         public String eval(Context c) throws Fault {
 593             return String.valueOf(left.evalNumber(c) >= right.evalNumber(c));
 594         }
 595 
 596         int precedence() {
 597             return PREC_GE;
 598         }
 599 
 600         @Override
 601         public String toString() {
 602             return "`" + left + ">=" + right + "'";
 603         }
 604     }
 605 
 606     //--------------------------------------------------------------------------
 607 
 608     static class LessExpr extends BinaryExpr {
 609 
 610         LessExpr(Expr left, Expr right) {
 611             super(left, right);
 612         }
 613 
 614         public String eval(Context c) throws Fault {
 615             return String.valueOf(left.evalNumber(c) < right.evalNumber(c));
 616         }
 617 
 618         int precedence() {
 619             return PREC_LT;
 620         }
 621 
 622         @Override
 623         public String toString() {
 624             return "`" + left + "<" + right + "'";
 625         }
 626     }
 627 
 628     //--------------------------------------------------------------------------
 629 
 630     static class LessEqualExpr extends BinaryExpr {
 631 
 632         LessEqualExpr(Expr left, Expr right) {
 633             super(left, right);
 634         }
 635 
 636         public String eval(Context c) throws Fault {
 637             return String.valueOf(left.evalNumber(c) <= right.evalNumber(c));
 638         }
 639 
 640         int precedence() {
 641             return PREC_LE;
 642         }
 643 
 644         @Override
 645         public String toString() {
 646             return "`" + left + "<=" + right + "'";
 647         }
 648     }
 649 
 650     //--------------------------------------------------------------------------
 651 
 652     static class MatchExpr extends BinaryExpr {
 653 
 654         MatchExpr(Expr left, Expr right) {
 655             super(left, right);
 656         }
 657 
 658         public String eval(Context c) throws Fault {
 659             return String.valueOf(left.eval(c).matches(right.eval(c)));
 660         }
 661 
 662         int precedence() {
 663             return PREC_EQ;
 664         }
 665 
 666         @Override
 667         public String toString() {
 668             return "`" + left + "~=" + right + "'";
 669         }
 670     }
 671 
 672     //--------------------------------------------------------------------------
 673 
 674     static class MultiplyExpr extends BinaryExpr {
 675 
 676         MultiplyExpr(Expr left, Expr right) {
 677             super(left, right);
 678         }
 679 
 680         public String eval(Context c) throws Fault {
 681             return String.valueOf(left.evalNumber(c) * right.evalNumber(c));
 682         }
 683 
 684         int precedence() {
 685             return PREC_MUL;
 686         }
 687 
 688         @Override
 689         public String toString() {
 690             return "`" + left + "*" + right + "'";
 691         }
 692     }
 693 
 694     //--------------------------------------------------------------------------
 695 
 696     static class NameExpr extends Expr {
 697 
 698         NameExpr(String name) {
 699             this.name = name;
 700         }
 701 
 702         public String eval(Context c) throws Fault {
 703             String v = c.get(name);
 704             if (v == null)
 705                 throw new Fault("name not defined: " + name);
 706             return v;
 707         }
 708 
 709         int precedence() {
 710             return PREC_TRM;
 711         }
 712 
 713         @Override
 714         public String toString() {
 715             return name;
 716         }
 717 
 718         private final String name;
 719     }
 720 
 721     //--------------------------------------------------------------------------
 722 
 723     static class NotEqualExpr extends BinaryExpr {
 724 
 725         NotEqualExpr(Expr left, Expr right) {
 726             super(left, right);
 727         }
 728 
 729         public String eval(Context c) throws Fault {
 730             return String.valueOf(!left.eval(c).equalsIgnoreCase(right.eval(c)));
 731         }
 732 
 733         int precedence() {
 734             return PREC_NE;
 735         }
 736 
 737         @Override
 738         public String toString() {
 739             return "`" + left + "!=" + right + "'";
 740         }
 741     }
 742 
 743     //--------------------------------------------------------------------------
 744 
 745     static class NotExpr extends Expr {
 746 
 747         NotExpr(Expr expr) {
 748             this.expr = expr;
 749         }
 750 
 751         public String eval(Context c) throws Fault {
 752             return String.valueOf(!expr.evalBoolean(c));
 753         }
 754 
 755         int precedence() {
 756             return PREC_NOT;
 757         }
 758 
 759         @Override
 760         public String toString() {
 761             return "!" + expr;
 762         }
 763 
 764         private final Expr expr;
 765     }
 766 
 767     //--------------------------------------------------------------------------
 768 
 769     static class NumberExpr extends Expr {
 770 
 771         NumberExpr(String value) {
 772             this.value = value;
 773         }
 774 
 775         public String eval(Context c) throws Fault {
 776             long scale;
 777             char lastCh = value.charAt(value.length() -1);
 778             switch (lastCh) {
 779                 case 'k': case 'K': scale = 1024; break;
 780                 case 'm': case 'M': scale = 1024 * 1024; break;
 781                 case 'g': case 'G': scale = 1024 * 1024 * 1024; break;
 782                 default:
 783                     return value;
 784             }
 785             try {
 786                 String s = value.substring(0, value.length() - 1);
 787                 return String.valueOf(Long.parseLong(s) * scale);
 788             } catch (NumberFormatException ex) {
 789                 throw new Fault("invalid numeric value: " + value);
 790             }
 791         }
 792 
 793         int precedence() {
 794             return PREC_NUM;
 795         }
 796 
 797         @Override
 798         public String toString() {
 799             return value;
 800         }
 801 
 802         private final String value;
 803     }
 804 
 805     //--------------------------------------------------------------------------
 806 
 807     static class OrExpr extends BinaryExpr {
 808 
 809         OrExpr(Expr left, Expr right) {
 810             super(left, right);
 811         }
 812 
 813         public String eval(Context c) throws Fault {
 814             return String.valueOf(left.evalBoolean(c) | right.evalBoolean(c));
 815         }
 816 
 817         int precedence() {
 818             return PREC_OR;
 819         }
 820 
 821         @Override
 822         public String toString() {
 823             return "`" + left + "|" + right + "'";
 824         }
 825     }
 826 
 827     //--------------------------------------------------------------------------
 828 
 829     static class ParenExpr extends Expr {
 830 
 831         ParenExpr(Expr expr) {
 832             this.expr = expr;
 833         }
 834 
 835         public String eval(Context c) throws Fault {
 836             return expr.eval(c);
 837         }
 838 
 839         int precedence() {
 840             return PREC_PRN;
 841         }
 842 
 843         @Override
 844         public String toString() {
 845             return "(" + expr + ")";
 846         }
 847 
 848         private final Expr expr;
 849     }
 850 
 851     //--------------------------------------------------------------------------
 852 
 853     static class RemainderExpr extends BinaryExpr {
 854 
 855         RemainderExpr(Expr left, Expr right) {
 856             super(left, right);
 857         }
 858 
 859         public String eval(Context c) throws Fault {
 860             return String.valueOf(left.evalNumber(c) % right.evalNumber(c));
 861         }
 862 
 863         int precedence() {
 864             return PREC_REM;
 865         }
 866 
 867         @Override
 868         public String toString() {
 869             return "`" + left + "%" + right + "'";
 870         }
 871     }
 872 
 873     //--------------------------------------------------------------------------
 874 
 875     static class StringExpr extends Expr {
 876 
 877         StringExpr(String value) {
 878             this.value = value;
 879         }
 880 
 881         public String eval(Context c) throws Fault {
 882             return value;
 883         }
 884 
 885         int precedence() {
 886             return PREC_LIT;
 887         }
 888 
 889         @Override
 890         public String toString() {
 891             return '"' + value + '"';
 892         }
 893 
 894         private final String value;
 895     }
 896 
 897     //--------------------------------------------------------------------------
 898 
 899     static class SubtractExpr extends BinaryExpr {
 900 
 901         SubtractExpr(Expr left, Expr right) {
 902             super(left, right);
 903         }
 904 
 905         public String eval(Context c) throws Fault {
 906             return String.valueOf(left.evalNumber(c) - right.evalNumber(c));
 907         }
 908 
 909         int precedence() {
 910             return PREC_SUB;
 911         }
 912 
 913         @Override
 914         public String toString() {
 915             return "`" + left + "-" + right + "'";
 916         }
 917     }
 918 
 919 }