1 /*
   2  * Copyright (c) 2000, 2016, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import java.io.*;
  25 import java.util.*;
  26 
  27 /*
  28  * assignment : key = value;
  29  * key        : string
  30  * value      : string | array | dict
  31  * nValue     : , value
  32  * array      : ( value nValue )
  33  * nAssignment: , assignment|value
  34  * dict       : { assignment* }
  35  * string     : "*" or anything but a ,(){}=
  36  *
  37  * special characters: ,(){}=
  38  */
  39 
  40 public class PParser {
  41     protected static final int      OPEN_PAIR = 1;
  42     protected static final int      CLOSE_PAIR = 2;
  43     protected static final int      OPEN_ARRAY = 3;
  44     protected static final int      CLOSE_ARRAY = 4;
  45     protected static final int      MORE = 5;
  46     protected static final int      EQUAL = 6;
  47     protected static final int      STRING = 7;
  48     protected static final int      WS = 8;
  49 
  50     protected Reader          reader;
  51     protected boolean         bufferedToken;
  52     protected StringBuffer    stringBuffer = new StringBuffer();
  53     protected int             lastChar;
  54     protected int             lastToken;
  55     protected int             lineNumber;
  56     protected int             column;
  57 
  58     public PParser() {
  59     }
  60 
  61     public Map<String,Object> parse(Reader r) throws IOException {
  62         this.reader = r;
  63         bufferedToken = false;
  64         lineNumber = 0;
  65         column = 0;
  66         if (getToken() != OPEN_PAIR) {
  67             error("No initial open");
  68         }
  69         return parsePair();
  70     }
  71 
  72     protected Object parseValue(int lookAhead) throws IOException {
  73         int           token;
  74 
  75         if (lookAhead == -1) {
  76             token = getToken();
  77         } else {
  78             token = lookAhead;
  79         }
  80         switch (token) {
  81         case STRING:
  82             return stringBuffer.toString();
  83         case OPEN_ARRAY:
  84             return parseArray();
  85         case OPEN_PAIR:
  86             return parsePair();
  87         default:
  88             error("Expecting value");
  89         }
  90         return null;
  91     }
  92 
  93     protected Object parseArray() throws IOException {
  94         List<Object> array = new ArrayList<>();
  95         int token;
  96 
  97         while ((token = getToken()) != CLOSE_ARRAY) {
  98             if (token == MORE) {
  99                 token = getToken();
 100             }
 101             if (token != CLOSE_ARRAY) {
 102                 array.add(parseValue(token));
 103             }
 104         }
 105         return array;
 106     }
 107 
 108     protected Map<String,Object> parsePair() throws IOException {
 109         Map<String,Object> ht = new HashMap<>(11);
 110         int token;
 111 
 112         while ((token = getToken()) != CLOSE_PAIR) {
 113             if (token != STRING) {
 114                 error("Pair expecting string got");
 115             }
 116             String     key = stringBuffer.toString();
 117 
 118             if (getToken() != EQUAL) {
 119                 error("Expecting = ");
 120             }
 121 
 122             Object     value = parseValue(-1);
 123             ht.put(key, value);
 124         }
 125         return ht;
 126     }
 127 
 128     protected void ungetToken() {
 129         if (bufferedToken) {
 130             error("Can not buffer more than one token");
 131         }
 132         bufferedToken = true;
 133     }
 134 
 135     protected int getToken() throws IOException {
 136         int token = getToken(false, false);
 137 
 138         return token;
 139     }
 140 
 141     @SuppressWarnings("fallthrough")
 142     protected int getToken(boolean wantsWS, boolean inString)
 143         throws IOException {
 144         if (bufferedToken) {
 145             bufferedToken = false;
 146             if (lastToken != WS || wantsWS) {
 147                 return lastToken;
 148             }
 149         }
 150         while ((lastChar = reader.read()) != -1) {
 151             // If a line starts with '#', skip the line.
 152             if (column == 0 && lastChar == '#') {
 153                 while ((lastChar = reader.read()) != -1
 154                        && lastChar != '\n') {
 155                 }
 156                 if (lastChar == -1) {
 157                     break;
 158                 }
 159             }
 160 
 161             column++;
 162             switch(lastChar) {
 163             case '\n':
 164                 lineNumber++;
 165                 column = 0;
 166             case ' ':
 167             case '\r':
 168             case '\t':
 169                 if (wantsWS) {
 170                     lastToken = WS;
 171                     return WS;
 172                 }
 173                 break;
 174             case ',':
 175                 lastToken = MORE;
 176                 return MORE;
 177             case '(':
 178                 lastToken = OPEN_ARRAY;
 179                 return OPEN_ARRAY;
 180             case ')':
 181                 lastToken = CLOSE_ARRAY;
 182                 return CLOSE_ARRAY;
 183             case '{':
 184                 lastToken = OPEN_PAIR;
 185                 return OPEN_PAIR;
 186             case '}':
 187                 lastToken = CLOSE_PAIR;
 188                 return CLOSE_PAIR;
 189             case '=':
 190                 lastToken = EQUAL;
 191                 return EQUAL;
 192             case '"':
 193                 lastToken = STRING;
 194                 if (!inString) {
 195                     stringBuffer.setLength(0);
 196                     while (true) {
 197                         getToken(true, true);
 198                         if (lastChar == '"') {
 199                             lastToken = STRING;
 200                             return STRING;
 201                         }
 202                         stringBuffer.append((char)lastChar);
 203                     }
 204                 }
 205                 return STRING;
 206             default:
 207                 lastToken = STRING;
 208                 if (!inString) {
 209                     stringBuffer.setLength(0);
 210                     stringBuffer.append((char)lastChar);
 211                     while (getToken(true, true) == STRING) {
 212                         if (lastChar == '"') {
 213                             error("Unexpected quote");
 214                         }
 215                         stringBuffer.append((char)lastChar);
 216                     }
 217                     ungetToken();
 218                 }
 219                 return STRING;
 220             }
 221         }
 222         return -1;
 223     }
 224 
 225     protected void error(String errorString) {
 226         throw new RuntimeException(errorString + " at line " + lineNumber + " column " + column);
 227     }
 228 
 229     @SuppressWarnings("unchecked")
 230     public static void dump(Object o) {
 231         if (o instanceof String) {
 232             System.out.print(o);
 233         } else if(o instanceof List) {
 234             dump(" (");
 235             ((List)o).forEach((l) -> {
 236                 dump(l);
 237                 dump(" -- ");
 238             });
 239             dump(" )");
 240         } else {
 241             Map<String,Object> ht = (Map<String,Object>)o;
 242             dump(" {");
 243             ht.keySet().forEach(l -> {
 244                 dump(l);
 245                 dump(" = ");
 246                 dump(ht.get(l));
 247                 dump(";");
 248             });
 249             dump(" }");
 250         }
 251     }
 252 
 253     public static void main(String[] args) {
 254         if (args.length == 0) {
 255             System.out.println("need filename");
 256         } else {
 257             try {
 258                 FileReader fr = new FileReader(args[0]);
 259                 PParser parser = new PParser();
 260                 Map<String,Object> ht = parser.parse(fr);
 261 
 262                 dump(ht);
 263                 System.out.println();
 264             }
 265             catch (IOException ioe) {
 266                 System.out.println("Couldn't parse: " + ioe);
 267             }
 268         }
 269     }
 270 }